aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--apps/ChangePassword/api.js9
-rw-r--r--apps/ChangePassword/components/App.js113
-rw-r--r--apps/ChangePassword/index.js3
-rw-r--r--apps/ChangePassword/styles/ChangePassword.module.scss27
-rw-r--r--apps/Settings/api.js4
-rw-r--r--apps/index.js1
-rw-r--r--components/Header.js28
-rw-r--r--configs/appList.js3
-rw-r--r--configs/sendMail.js2
-rw-r--r--configs/translations.js44
-rw-r--r--helpers/email.js20
-rw-r--r--models/User.js23
-rw-r--r--pages/api/password.js35
-rw-r--r--pages/api/register.js4
-rw-r--r--pages/api/settings.js1
-rw-r--r--pages/api/verify.js4
16 files changed, 300 insertions, 21 deletions
diff --git a/apps/ChangePassword/api.js b/apps/ChangePassword/api.js
new file mode 100644
index 0000000..ef609a7
--- /dev/null
+++ b/apps/ChangePassword/api.js
@@ -0,0 +1,9 @@
+import fetchJson from 'helpers/fetchJson'
+
+export const changePassword = async (data) => (
+ await fetchJson('/api/password', {
+ method: 'POST',
+ headers: { 'Content-Type': 'plain/text; charset=utf-8' },
+ body: JSON.stringify(data)
+ })
+)
diff --git a/apps/ChangePassword/components/App.js b/apps/ChangePassword/components/App.js
new file mode 100644
index 0000000..ce32ffb
--- /dev/null
+++ b/apps/ChangePassword/components/App.js
@@ -0,0 +1,113 @@
+import styles from '../styles/ChangePassword.module.scss'
+import { useState } from 'react'
+import useSettings from 'hooks/useSettings'
+import usePopup from 'hooks/usePopup'
+import useApps from 'hooks/useApps'
+import useUser from 'hooks/useUser'
+import { close } from 'helpers/windowActions'
+import { changePassword } from '../api'
+
+const App = () => {
+ const { t } = useSettings()
+ const { user } = useUser()
+ const { setApps } = useApps()
+ const { setPopup } = usePopup()
+ const [currPassError, setCurrPassError] = useState(false)
+ const [newPassError, setNewPassError] = useState(false)
+
+ const onSubmit = async (e) => {
+ e.preventDefault()
+
+ const {
+ current_password: {value: currentPassword},
+ new_password: { value: newPassword},
+ confirm_password: {value: confirmPassword}
+ } = e.currentTarget
+
+ if (newPassword !== confirmPassword) {
+ setPopup({
+ content: t('change_password_wrong_new'),
+ time: 2000,
+ error: true
+ })
+ setNewPassError(true)
+
+ } else if (newPassword === currentPassword) {
+ setPopup({
+ content: t('change_password_same_password'),
+ time: 2000,
+ error: true
+ })
+ setNewPassError(true)
+ } else {
+ try {
+ await changePassword({_id: user._id, currentPassword, newPassword})
+ setPopup({
+ content: t('change_password_changed'),
+ time: 2000,
+ })
+ close('ChangePassword', setApps)
+ } catch(e) {
+ if (e?.data?.error === 'Wrong password') {
+ setPopup({
+ content: t('change_password_wrong_current'),
+ time: 2000,
+ error: true
+ })
+ setCurrPassError(true)
+ } else {
+ setPopup({
+ content: t('change_password_error'),
+ time: 2000,
+ error: true
+ })
+ }
+ }
+ }
+ }
+
+ return (
+ <form className={styles.password} onSubmit={onSubmit}>
+ <label className={styles.label} htmlFor='current_password'>
+ {t('change_password_current')}
+ </label>
+ <input
+ className={`${styles.input} ${currPassError ? styles.error : ''}`}
+ id='current_password'
+ type='password'
+ name='current_password'
+ title={t('change_password_current')}
+ onChange={() => { currPassError && setCurrPassError(false) }}
+ required
+ />
+ <label className={styles.label} htmlFor='new_password'>
+ {t('change_password_new')}
+ </label>
+ <input
+ className={`${styles.input} ${newPassError ? styles.error : ''}`}
+ id='new_password'
+ type='password'
+ name='new_password'
+ title={t('change_password_new')}
+ onChange={() => { newPassError && setNewPassError(false) }}
+ required
+ />
+ <input
+ className={`${styles.input} ${newPassError ? styles.error : ''}`}
+ type='password'
+ name='confirm_password'
+ placeholder={t('change_password_confirm')}
+ title={t('change_password_confirm')}
+ onChange={() => { newPassError && setNewPassError(false) }}
+ required
+ />
+ <input
+ className='window__button'
+ type='submit'
+ value={t('save')}
+ />
+ </form>
+ )
+}
+
+export default App
diff --git a/apps/ChangePassword/index.js b/apps/ChangePassword/index.js
new file mode 100644
index 0000000..cd1d5e2
--- /dev/null
+++ b/apps/ChangePassword/index.js
@@ -0,0 +1,3 @@
+import ChangePassword from './components/App'
+
+export default ChangePassword
diff --git a/apps/ChangePassword/styles/ChangePassword.module.scss b/apps/ChangePassword/styles/ChangePassword.module.scss
new file mode 100644
index 0000000..cb4e876
--- /dev/null
+++ b/apps/ChangePassword/styles/ChangePassword.module.scss
@@ -0,0 +1,27 @@
+.password {
+ padding: 1em;
+ text-align: center;
+}
+
+.input {
+ color: var(--color-text-alt);
+ background: var(--color-window-content);
+ padding: .5em;
+ margin: .75em 0;
+ border: 1px dashed var(--color-decor);
+ border-radius: .5px;
+ width: 100%;
+}
+
+.label {
+ font-size: .9em;
+ display: block;
+ text-align: left;
+ color: var(--color-text-alt);
+ margin-top: .75em;
+}
+
+.error {
+ border: 1px solid var(--color-error);
+ color: var(--color-error);
+}
diff --git a/apps/Settings/api.js b/apps/Settings/api.js
index f538368..142afc1 100644
--- a/apps/Settings/api.js
+++ b/apps/Settings/api.js
@@ -1,9 +1,9 @@
import fetchJson from 'helpers/fetchJson'
-export const saveSettings = async (data) => {
+export const saveSettings = async (data) => (
fetchJson('/api/settings', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
})
-}
+)
diff --git a/apps/index.js b/apps/index.js
index eccedac..05ad5e4 100644
--- a/apps/index.js
+++ b/apps/index.js
@@ -2,4 +2,5 @@ export { default as Calculator } from './Calculator'
export { default as Notes } from './Notes'
export { default as Player } from './Player'
export { default as Settings } from './Settings'
+export { default as ChangePassword } from './ChangePassword'
export { default as Youtube } from './Youtube'
diff --git a/components/Header.js b/components/Header.js
index 2db8178..60bf1bd 100644
--- a/components/Header.js
+++ b/components/Header.js
@@ -98,15 +98,25 @@ const Header = () => {
<div className={styles.headerOverlay} onClick={() => setUserMenu(false)} />
<ul className={styles.submenu}>
{user.isVerified && (
- <li>
- <span onClick={() => {
- open({ appName: 'Settings', ...appList.Settings }, setApps)
- setUserMenu()
- }}
- >
- {t('Settings')}
- </span>
- </li>
+ <>
+ <li>
+ <span onClick={() => {
+ open({ appName: 'ChangePassword', ...appList.ChangePassword }, setApps)
+ setUserMenu()
+ }}>
+ {t('ChangePassword')}
+ </span>
+ </li>
+ <li>
+ <span onClick={() => {
+ open({ appName: 'Settings', ...appList.Settings }, setApps)
+ setUserMenu()
+ }}
+ >
+ {t('Settings')}
+ </span>
+ </li>
+ </>
)}
<li>
<span onClick={e => handleLogout(e, router, mutateUser)}>
diff --git a/configs/appList.js b/configs/appList.js
index 13ca6d6..7963648 100644
--- a/configs/appList.js
+++ b/configs/appList.js
@@ -1,9 +1,10 @@
-import { Calculator, Notes, Player, Settings, Youtube } from 'apps'
+import { Calculator, Notes, Player, Settings, ChangePassword, Youtube } from 'apps'
const appList = {
Calculator: { component: Calculator, icon: true, buttons: ['min', 'close'], height: '30em', width: '20em' },
Notes: { component: Notes, icon: true, buttons: ['min', 'max', 'close'], height: '48em', width: '64em' },
Player: { component: Player, icon: true, buttons: ['min', 'max', 'close'], height: '48em', width: '64em' },
+ ChangePassword: { component: ChangePassword, icon: false, buttons: ['min', 'close'], height: '21em', width: '18em' },
Settings: { component: Settings, icon: false, buttons: ['min', 'close'], height: '23em', width: '16em' },
Youtube: { component: Youtube, icon: true, buttons: ['min', 'max', 'close'], height: '48em', width: '64em' }
}
diff --git a/configs/sendMail.js b/configs/sendMail.js
index 6405607..c026f0c 100644
--- a/configs/sendMail.js
+++ b/configs/sendMail.js
@@ -1,6 +1,6 @@
import nodemailer from 'nodemailer'
-const sendMail = (to, subject, text, html) => {
+const sendMail = ({to, subject, text, html}) => {
const transporter = nodemailer.createTransport({
port: 465,
host: process.env.MYAPPS_MAIL_SMTP_SERVER,
diff --git a/configs/translations.js b/configs/translations.js
index 1b8ed40..b496719 100644
--- a/configs/translations.js
+++ b/configs/translations.js
@@ -36,12 +36,15 @@ const translations = {
Calculator: 'Calculator',
Notes: 'Notes',
Player: 'Player',
+ ChangePassword: 'Change Password',
Settings: 'Settings',
Youtube: 'Youtube',
mail_ver_subject: 'Verification of your new apps.pruss.it account',
mail_ver_t1: 'Thank you for creating an account in My Apps.',
mail_ver_t2: 'We are sending you the verification code:',
mail_ver_t3: 'To finish verification log in and paste this code.',
+ mail_pass_subject: 'Your password has been changed',
+ mail_pass_t1: 'The password to your apps.pruss.it account was successfully changed.',
verification_mail_error: 'Could not send verification email',
verification_error: 'Could not verify user',
verification_title: 'One last step missing!',
@@ -79,6 +82,14 @@ const translations = {
player_playlist_default: 'Default playlist',
player_item_playing: 'Playing in Player',
player_item_enqueued: 'Enqueued in Player',
+ change_password_current: 'Current password',
+ change_password_new: 'New password',
+ change_password_confirm: 'Confirm new password',
+ change_password_changed: 'Password successfully changed',
+ change_password_wrong_current: 'Wrong current password',
+ change_password_wrong_new: 'New passwords do not match',
+ change_password_same_password: 'New password cannot be the same like previous password',
+ change_password_error: 'Error while changing password',
yt_video: 'Videos',
yt_video_count: ' videos',
yt_live: 'Live',
@@ -125,11 +136,14 @@ const translations = {
Notes: 'Notatki',
Player: 'Odtwarzacz',
Settings: 'Ustawienia',
+ ChangePassword: 'Zmień hasło',
Youtube: 'Youtube',
mail_ver_subject: 'Weryfikacja Twojego nowego konta w apps.pruss.it.',
mail_ver_t1: 'Dziękujemy za założenie konta w My Apps',
mail_ver_t2: 'Przesyłamy Ci kod weryfikacyjny:',
mail_ver_t3: 'W celu zakończenia weryfikacji zaloguj się i podaj ten kod.',
+ mail_pass_subject: 'Twoje hasło zostało zmienione',
+ mail_pass_t1: 'Hasło do Twojego konta apps.pruss.it zostało zmienione.',
verification_mail_error: 'Błąd podczas wysyłania maila z kodem weryfikacyjnym',
verification_error: 'Błąd podczas weryfikacji użytkownika',
verification_title: 'Pozostał ostatni krok!',
@@ -167,6 +181,14 @@ const translations = {
player_playlist_default: 'Domyślna lista',
player_item_playing: 'Odtwarzanie w Odtwarzaczu',
player_item_enqueued: 'Dodano do kolejki odtwarzania',
+ change_password_current: 'Obecne hasło',
+ change_password_new: 'Nowe hasło',
+ change_password_confirm: 'Powtórz nowe hasło',
+ change_password_changed: 'Hasło zostało zmienione',
+ change_password_wrong_current: 'Błędne obecne hasło',
+ change_password_wrong_new: 'Nowe hasła nie zgadzają się',
+ change_password_same_password: 'Nowe hasło musi różnić się od poprzedniego',
+ change_password_error: 'Bład podczas zmiany hasła',
yt_video: 'Wideo',
yt_video_count: ' klipów',
yt_live: 'Na żywo',
@@ -213,11 +235,14 @@ const translations = {
Notes: 'Notas',
Player: 'Jugador',
Settings: 'Ajustes',
+ ChangePassword: 'Cambio de contraseña',
Youtube: 'Youtube',
mail_ver_subject: 'Verificación de su nueva cuenta en apps.pruss.it.',
mail_ver_t1: 'Gracias por crear una cuenta con Mis aplicaciones',
mail_ver_t2: 'Te enviamos un código de verificación: ',
mail_ver_t3: 'Para completar la verificación, inicie sesión e ingrese este código.',
+ mail_pass_subject: 'Tu contraseña ha sido cambiada',
+ mail_pass_t1: 'La contraseña de su cuenta apps.pruss.it se cambió con éxito.',
verification_mail_error: 'Error al enviar el correo electrónico con el código de verificación',
verification_error: 'Error de verificación del usuario',
verification_title: '¡Queda un último paso!',
@@ -255,6 +280,14 @@ const translations = {
player_playlist_default: 'Lista predeterminada',
player_item_playing: 'Jugar con el Jugador',
player_item_enqueued: 'Añadido a la cola de reproducción',
+ change_password_current: 'Contraseña actual',
+ change_password_new: 'Contraseña nueva',
+ change_password_confirm: 'Repetir contraseña nueva',
+ change_password_changed: 'La contraseña ha sido cambiada',
+ change_password_wrong_current: 'Contraseña actual incorrecta',
+ change_password_wrong_new: 'Las nuevas contraseñas no coinciden',
+ change_password_same_password: 'La nueva contraseña debe ser diferente a la anterior',
+ change_password_error: 'Error al cambiar la contraseña',
yt_video: 'Video',
yt_video_count: ' clips',
yt_live: 'En vivo',
@@ -301,11 +334,14 @@ const translations = {
Notes: 'Anmerkungen',
Player: 'Spieler',
Settings: 'Einstellungen',
+ ChangePassword: 'Passwortänderung',
Youtube: 'Youtube',
mail_ver_subject: 'Bestätigen Ihres neuen Kontos in der apps.pruss.it.',
mail_ver_t1: 'Vielen Dank, dass Sie ein Konto bei My Apps erstellt haben',
mail_ver_t2: 'Wir senden Ihnen einen Bestätigungscode: ',
mail_ver_t3: 'Um die Verifizierung abzuschließen, loggen Sie sich ein und geben Sie diesen Code ein.',
+ mail_pass_subject: 'Ihr Passwort wurde geändert',
+ mail_pass_t1: 'Das Passwort für Ihr apps.pruss.it-Konto wurde erfolgreich geändert.',
verification_mail_error: 'Fehler beim Senden der E-Mail mit dem Bestätigungscode',
verification_error: 'Fehler bei der Benutzerüberprüfung',
verification_title: 'Ein letzter Schritt bleibt!',
@@ -343,6 +379,14 @@ const translations = {
player_playlist_default: 'Standardliste',
player_item_playing: 'Spiele auf dem Spieler',
player_item_enqueued: 'Zur Play Queue hinzugefügt',
+ change_password_current: 'Aktuelles Passwort',
+ change_password_new: 'Neues Passwort',
+ change_password_confirm: 'Wiederhole neues Passwort',
+ change_password_changed: 'Passwort erfolgreich geändert',
+ change_password_wrong_current: 'Falsches aktuelles Passwort',
+ change_password_wrong_new: 'Neue Passwörter stimmen nicht überein, Neue Passwörter passen nicht',
+ change_password_same_password: 'Das neue Passwort darf nicht mit dem vorherigen Passwort identisch sein',
+ change_password_error: 'Fehler beim Ändern des Passworts, Fehler beim Passwort ändern',
yt_video: 'Video',
yt_video_count: ' Clips',
yt_live: 'Live',
diff --git a/helpers/email.js b/helpers/email.js
index 52815f5..5373d8a 100644
--- a/helpers/email.js
+++ b/helpers/email.js
@@ -1,5 +1,19 @@
import { t } from 'configs/translations'
-export const subject = l => t(l, 'mail_ver_subject')
-export const text = (l, key) => `${t(l, 'mail_ver_t1')}\n${t(l, 'mail_ver_t2')}\n\n${key}\n\n${t(l, 'mail_ver_t3')}`
-export const html = (l, key) => `<p>${t(l, 'mail_ver_t1')}<br/>${t(l, 'mail_ver_t2')}</p><p style="font-size: 150%;padding: 1em;border: 1px solid black">${key}</p><p>${t(l, 'mail_ver_t3')}</p></br>`
+const mailData = (type, l, d) => {
+ switch(type) {
+ case 'v': return {
+ subject: t(l, 'mail_ver_subject'),
+ text: `${t(l, 'mail_ver_t1')}\n${t(l, 'mail_ver_t2')}\n\n${d}\n\n${t(l, 'mail_ver_t3')}`,
+ html: `<p>${t(l, 'mail_ver_t1')}<br/>${t(l, 'mail_ver_t2')}</p><p style="font-size: 150%;padding: 1em;border: 1px solid black">${key}</p><p>${t(l, 'mail_ver_t3')}</p></br>`,
+ }
+ case 'p': return {
+ subject: t(l, 'mail_pass_subject'),
+ text: `${t(l, 'mail_pass_t1')}\n`,
+ html: `<p>${t(l, 'mail_pass_t1')}<br/></p></br>`,
+ }
+ default:
+ }
+}
+
+export default mailData
diff --git a/models/User.js b/models/User.js
index bcf2523..4992b20 100644
--- a/models/User.js
+++ b/models/User.js
@@ -97,6 +97,29 @@ userSchema.statics.saveSettings = async function ({ _id, theme, language }) {
return userResponse(user)
}
+userSchema.statics.savePassword = async function ({ _id, currentPassword, newPassword }) {
+ const user = await User.findOne({ _id })
+
+ if (!user) {
+ throw new Error('Unable to change password')
+ }
+
+ const isMatch = await bcrypt.compare(currentPassword, user.password)
+
+ if (!isMatch) {
+ throw new Error('Wrong password')
+ }
+
+ const password = await bcrypt.hash(newPassword, 8)
+ const newUser = await User.findOneAndUpdate({ _id }, { password }, { new: true })
+
+ if (!newUser) {
+ throw new Error('Could not update password')
+ }
+
+ return userResponse(newUser)
+}
+
userSchema.statics.state = async function (_id) {
const user = await User.findOne({ _id })
diff --git a/pages/api/password.js b/pages/api/password.js
new file mode 100644
index 0000000..871c9e6
--- /dev/null
+++ b/pages/api/password.js
@@ -0,0 +1,35 @@
+import dbConnect from 'configs/dbConnect'
+import withSession from 'hocs/withSession'
+import sendMail from 'configs/sendMail'
+import mailData from 'helpers/email'
+import User from 'models/User'
+
+export default withSession(async (req, res) => {
+ await dbConnect()
+
+ switch (req.method) {
+ case 'POST':
+ try {
+ const data = JSON.parse(req.body)
+ if (data._id && data.currentPassword && data.newPassword) {
+ const user = await User.savePassword(data)
+ req.session.set('user', user)
+ await req.session.save()
+
+ sendMail({to: user.email, ...mailData('p', user.language)})
+
+ res.status(200).json(user)
+ }
+ } catch (error) {
+ if (error?.message === 'Wrong password') {
+ res.status(401).send({ error: 'Wrong password' })
+ } else {
+ res.status(400).send({ error })
+ }
+ }
+ break
+ default:
+ res.status(400).send({ error })
+ break
+ }
+})
diff --git a/pages/api/register.js b/pages/api/register.js
index 2c866de..56c8ba9 100644
--- a/pages/api/register.js
+++ b/pages/api/register.js
@@ -1,7 +1,7 @@
import dbConnect from 'configs/dbConnect'
import sendMail from 'configs/sendMail'
import withSession from 'hocs/withSession'
-import { subject, text, html } from 'helpers/email'
+import mailData from 'helpers/email'
import User from 'models/User'
import NoteList from 'apps/Notes/models/NoteList'
@@ -20,7 +20,7 @@ export default withSession(async (req, res) => {
if (!email) { throw new Error('Something went wrong') }
- sendMail(body.email, subject(language), text(language, key), html(language, key))
+ sendMail({to: body.email, ...mailData('v', language, key)})
const user = { _id, email, noteList, theme, language, isVerified: false, isLoggedIn: true }
req.session.set('user', user)
diff --git a/pages/api/settings.js b/pages/api/settings.js
index f0d8e06..868f957 100644
--- a/pages/api/settings.js
+++ b/pages/api/settings.js
@@ -15,7 +15,6 @@ export default withSession(async (req, res) => {
res.status(200).json(user)
}
} catch (error) {
- console.log(error)
res.status(400).send()
}
break
diff --git a/pages/api/verify.js b/pages/api/verify.js
index 89bd4cd..edcc7a9 100644
--- a/pages/api/verify.js
+++ b/pages/api/verify.js
@@ -2,7 +2,7 @@ import dbConnect from 'configs/dbConnect'
import sendMail from 'configs/sendMail'
import User from 'models/User'
import withSession from 'hocs/withSession'
-import { subject, text, html } from 'helpers/email'
+import mailData from 'helpers/email'
export default withSession(async (req, res) => {
await dbConnect()
@@ -16,7 +16,7 @@ export default withSession(async (req, res) => {
const key = await User.getVerificationKey(email)
if (!key) { throw new Error('Something went wrong') }
- const response = await sendMail(email, subject(l), text(l, key), html(l, key))
+ const response = await sendMail({to: email, ...mailData('v', l, key)})
if (!response?.accepted?.length) { throw new Error('Something went wrong') }
res.status(204).send()