From 3043135f460fbd87e12b7f3585697c29cd146b67 Mon Sep 17 00:00:00 2001 From: kernoeb Date: Wed, 20 Sep 2023 13:32:38 +0200 Subject: [PATCH] fix: update favorites plannings, security check --- components/DialogSettings.vue | 12 +++++------ components/SelectPlanning.vue | 15 +++++++++++--- server/index.js | 9 ++++---- server/routes/subscriptions.js | 38 +++++++++++++++++++++------------- 4 files changed, 46 insertions(+), 28 deletions(-) diff --git a/components/DialogSettings.vue b/components/DialogSettings.vue index 6d37f44..b80c53e 100644 --- a/components/DialogSettings.vue +++ b/components/DialogSettings.vue @@ -471,14 +471,14 @@ export default { const subscription = await registration.pushManager.getSubscription() await subscription.unsubscribe() - await fetch('/api/v1/subscriptions/unsubscribe', { - method: 'POST', - body: JSON.stringify(subscription), + await this.$axios.$post('/api/v1/subscriptions/unsubscribe', subscription, { headers: { 'content-type': 'application/json' } }) + localStorage.removeItem('subscription') + console.log('Unregistered push') this.notifications = false this.loadingSubscription = false @@ -509,10 +509,10 @@ export default { subscription.plannings = [] } + localStorage.setItem('subscription', JSON.stringify(subscription)) + console.log('Sending push') - await fetch('/api/v1/subscriptions/subscribe', { - method: 'POST', - body: JSON.stringify(subscription), + await this.$axios.$post('/api/v1/subscriptions/subscribe', subscription, { headers: { 'content-type': 'application/json' } diff --git a/components/SelectPlanning.vue b/components/SelectPlanning.vue index f9d27af..dfb08e6 100644 --- a/components/SelectPlanning.vue +++ b/components/SelectPlanning.vue @@ -412,7 +412,6 @@ export default { const final = [...new Set(tmp)].filter(v => !!v) this.favorites = final this.$cookies.set('favorites', final.join(','), { maxAge: 2147483646 }) - this.getNames() this.$nextTick(() => { this.refreshFavorites() }) @@ -443,8 +442,6 @@ export default { this.menuGroup = false }, refreshFavorites () { - // TODO update push notifications - try { this.favorites = (this.$cookies?.get('favorites')?.split(',') || []).filter(v => !!v) @@ -457,6 +454,18 @@ export default { this.getNames() + try { + const subscription = JSON.parse(localStorage.getItem('subscription') || '{}') + if (subscription && subscription.endpoint) { + const plannings = [...this.favorites || []] + this.$axios.$put('/api/v1/subscriptions/update-plannings ', { subscription, plannings }).catch((err) => { + console.log('Error updating favorites for notifications', err) + }) + } + } catch (err) { + console.log('Error updating favorites for notifications', err) + } + if (this.$cookies?.get('favorites') === '') this.$cookies.remove('favorites') if (this.$cookies?.get('groupFavorites') === '') this.$cookies.remove('groupFavorites') } catch (err) {} diff --git a/server/index.js b/server/index.js index f887845..868b1a1 100644 --- a/server/index.js +++ b/server/index.js @@ -84,7 +84,7 @@ if (process.env.NODE_ENV !== 'test') { // Get distinct of subscriptions plannings const subscriptions = await Subscription.distinct('plannings') - console.log('Number of active subscriptions', subscriptions.length) + logger.info('[Subscriptions] Number of active plannings', subscriptions.length) if (subscriptions.length) { for (const planning of subscriptions) { @@ -107,6 +107,7 @@ if (process.env.NODE_ENV !== 'test') { const diff = dtstart - now const tenMinutes = 1000 * 60 * 10 + // const thirtyMinutes = 1000 * 60 * 30 // const twelveHours = 1000 * 60 * 60 * 12 if (diff <= tenMinutes) { @@ -116,7 +117,7 @@ if (process.env.NODE_ENV !== 'test') { const payload = JSON.stringify({ title: `Cours dans ${Math.round(diff / 1000 / 60)} minutes`, body: `- ${cleanName(nextEvent.summary.value)}\n- ${cleanLocation(nextEvent.location.value)}\n- ${cleanDescription(nextEvent.description.value)}`, - icon: 'https://planningsup.app/favicon.ico' + icon: '/favicon.ico' }) const date = new Date(dtstart) @@ -130,9 +131,7 @@ if (process.env.NODE_ENV !== 'test') { continue } - await retry(async () => { - await sendNotification(subscription, payload, true) - }, 3) // Retry 3 times + await retry(async () => await sendNotification(subscription, payload, true), 2) // Retry 2 times .then(() => updateSubscription(subscription, { id, date, status: 'sent' })) .catch(() => updateSubscription(subscription, { id, date, status: 'error' })) } diff --git a/server/routes/subscriptions.js b/server/routes/subscriptions.js index cf0f730..98173b8 100644 --- a/server/routes/subscriptions.js +++ b/server/routes/subscriptions.js @@ -7,30 +7,40 @@ const asyncWrap = require('async-wrapper-express-ts') const { sendNotification } = require('../util/notifications') const { Subscription } = require('../models/subscriptions') -// TODO clean up expired subscriptions every 1 hour +const checkPayload = (req, res, next) => { + if (!req.body || !req.body.plannings) return res.status(400).json({ message: 'Missing plannings' }) + if (!Array.isArray(req.body.plannings) || !req.body.plannings.every(p => typeof p === 'string')) return res.status(400).json({ message: 'Invalid plannings' }) + next() +} -router.post('/subscribe', asyncWrap(async (req, res) => { - const subscriptionBody = req.body - if (!subscriptionBody || !subscriptionBody.endpoint) return res.status(400).json({ message: 'Invalid subscription' }) - if (typeof subscriptionBody.endpoint !== 'string') return res.status(400).json({ message: 'Invalid subscription' }) - if (!subscriptionBody.plannings) return res.status(400).json({ message: 'Missing plannings' }) +router.post('/subscribe', checkPayload, asyncWrap(async (req, res) => { + const subscription = req.body + if (!subscription || !subscription.endpoint || typeof subscription.endpoint !== 'string') return res.status(400).json({ message: 'Invalid subscription' }) - const payload = JSON.stringify({ title: `Abonnement réussi pour ${subscriptionBody.plannings.length} planning(s)`, body: 'Vous recevrez une notification en temps voulu :)', icon: '/favicon.ico' }) - await sendNotification(subscriptionBody, payload, true) + const payload = JSON.stringify({ title: `Abonnement réussi pour ${subscription.plannings.length} planning(s)`, body: 'Vous recevrez une notification en temps voulu :)', icon: '/favicon.ico' }) + await sendNotification(subscription, payload, true) - const subscription = await Subscription.findOneAndUpdate({ endpoint: subscriptionBody.endpoint }, subscriptionBody, { new: true, upsert: true, runValidators: true }) + const result = await Subscription.findOneAndUpdate({ endpoint: subscription.endpoint }, subscription, { new: true, upsert: true, runValidators: true }) - return res.status(201).json({ message: 'Subscribed', subscription }) + return res.status(201).json({ message: 'Subscribed', subscription: result }) })) router.post('/unsubscribe', asyncWrap(async (req, res) => { - const subscriptionBody = req.body - if (!subscriptionBody || !subscriptionBody.endpoint) return res.status(400).json({ message: 'Invalid subscription' }) - if (typeof subscriptionBody.endpoint !== 'string') return res.status(400).json({ message: 'Invalid subscription' }) + const subscription = req.body + if (!subscription || !subscription.endpoint || typeof subscription.endpoint !== 'string') return res.status(400).json({ message: 'Invalid subscription' }) - await Subscription.deleteOne({ endpoint: subscriptionBody.endpoint }, { runValidators: true }) + await Subscription.deleteOne({ endpoint: subscription.endpoint }, { runValidators: true }) return res.status(200).json({ message: 'Unsubscribed' }) })) +router.put('/update-plannings', checkPayload, asyncWrap(async (req, res) => { + const { subscription, plannings } = req.body + if (!subscription || !subscription.endpoint || typeof subscription.endpoint !== 'string') return res.status(400).json({ message: 'Invalid subscription' }) + + await Subscription.updateOne({ endpoint: subscription.endpoint }, { $set: { plannings } }) + + return res.status(201) // do not leak +})) + module.exports = router