change license, add user settings, social account unlink
This commit is contained in:
parent
772d6aab1e
commit
497ac86980
29
LICENSE
29
LICENSE
@ -1,16 +1,21 @@
|
|||||||
Icy Network Primary Web Application - Authentication and News
|
MIT License
|
||||||
Copyright (C) 2017 Icy Network - Evert Prants <evert@lunasqu.ee>
|
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
Copyright (c) 2017 Evert Prants
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
in the Software without restriction, including without limitation the rights
|
||||||
GNU General Public License for more details.
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
The above copyright notice and this permission notice shall be included in all
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
@ -5,17 +5,17 @@
|
|||||||
<p>Icy Network may collect and save some information about our users.</p>
|
<p>Icy Network may collect and save some information about our users.</p>
|
||||||
<h3>Basic Account Information</h3>
|
<h3>Basic Account Information</h3>
|
||||||
<p>Icy Network uses your username to identify you in our system. Please only enter Usernames which you are comfortable with other people seeing.</p>
|
<p>Icy Network uses your username to identify you in our system. Please only enter Usernames which you are comfortable with other people seeing.</p>
|
||||||
<p>Your email addess is used to send you updates and important information about our Services and the status of your account. You may unsubscribe from update emails at any time. We will never provide, sell, or leak your email address to Third-Party sites nor send you malicious or spam emails. Please always verify that the sender is an IcyNet.eu email address before clicking on any links on emails claiming to be from us. We will never ask you for your personal information or passwords via email.</p>
|
<p>Your email addess is used to send you updates and important information about our Services and the status of your account. You may unsubscribe from update emails at any time. We will never provide, sell, or leak your email address to Third-Party sites nor send you malicious or spam emails. We will never ask you for your personal information or passwords via email.</p>
|
||||||
<h3>Additional Information</h3>
|
<h3>Additional Information</h3>
|
||||||
<p>We use your IP Address to track our visitor traffic in order for us to better allocate resources to the Services which are used the most. Your IP address is not available publicly and is only used internally within our systems.</p>
|
<p>We use your IP Address to track our visitor traffic in order for us to better allocate resources to the Services which are used the most. Your IP address is not available publicly and is only used internally within our systems.</p>
|
||||||
<h2>Cookies</h2>
|
<h2>Cookies</h2>
|
||||||
<p>Like many websites, we use Cookies in our Services. A Cookie is a small file saved onto your computer by your web browser which contains a bit of information about your presence on Icy Network websites. Icy Network uses temporary session cookies in order to save log-in sessions, which means that you won't have to log in every time you visit our website.</p>
|
<p>Like many websites, we use Cookies in our Services. A Cookie is a small file saved onto your computer by your web browser which contains a bit of information about your presence on Icy Network websites. Icy Network uses temporary session cookies, which are used to save log-in sessions, which means that you won't have to log in every time you visit our website.</p>
|
||||||
<h2>External Logins</h2>
|
<h2>External Logins</h2>
|
||||||
<p>By logging in from external websites, you agree to these Policies.</p>
|
<p>By logging in from external websites, you agree to these Policies.</p>
|
||||||
<h3>Twitter</h3>
|
<h3>Twitter</h3>
|
||||||
<p>By logging in with Twitter, we will only ask you for your Screen Name, Public Profile Name and Email Address for the above-mentioned purposes. We will never Tweet on your behalf nor see your Tweets.</p>
|
<p>By logging in with Twitter, we will only ask you for your Screen Name, Public Profile Name and Email Address for the above-mentioned purposes. We are unable to Tweet on your behalf nor see your Tweets.</p>
|
||||||
<h3>Facebook</h3>
|
<h3>Facebook</h3>
|
||||||
<p>By logging in with Facebook, we will only ask you for your Public Profile and Email Address. We will use your Name as your Display Name, which can be changed from your Account Settings after logging in. Your profile picture may be downloaded onto our servers and used as your network-wide profile image. You may change your profile picture from your Account Settings at any time. Your Email Address will only be used to send you updates, which you can opt-out of. We can not and will not post on your behalf.</p>
|
<p>By logging in with Facebook, we will only ask you for your Public Profile and Email Address. We will use your Name as your Display Name, which can be changed from your Account Settings after logging in. Your profile picture may be downloaded onto our servers and used as your network-wide profile image. You may change your profile picture from your Account Settings at any time. Your Email Address will only be used to send you updates, which you can opt-out of. We can not: post on your behalf, see your friends list nor see your posts.</p>
|
||||||
<h3>Discord</h3>
|
<h3>Discord</h3>
|
||||||
<p>By logging in with Discord, we will only ask you for your Username and Email Address for the above-mentioned purposes. We do not ask you for any other information and we will not know which Discord Servers you're on.</p>
|
<p>By logging in with Discord, we will only ask you for your Username and Email Address for the above-mentioned purposes. We do not ask you for any other information and we will not know which Discord Servers you're on.</p>
|
||||||
</div>
|
</div>
|
||||||
|
@ -14,20 +14,4 @@
|
|||||||
<h4>Credits</h4>
|
<h4>Credits</h4>
|
||||||
<p>Google Play and the Google Play logo are trademarks of Google Inc.</p>
|
<p>Google Play and the Google Play logo are trademarks of Google Inc.</p>
|
||||||
<p>Apple and the Apple logo are trademarks of Apple Inc., registered in the U.S. and other countries. App Store is a service mark of Apple Inc., registered in the U.S. and other countries.</p>
|
<p>Apple and the Apple logo are trademarks of Apple Inc., registered in the U.S. and other countries. App Store is a service mark of Apple Inc., registered in the U.S. and other countries.</p>
|
||||||
<h2>Icy Network Software License</h2>
|
|
||||||
<a href="https://github.com/IcyNet/IcyNet.eu" target="_blank">Icy Network Primary Web Application - Authentication and News</a><br>
|
|
||||||
Copyright (C) 2017 Icy Network - Evert Prants <evert@lunasqu.ee><br>
|
|
||||||
<br>
|
|
||||||
This program is free software: you can redistribute it and/or modify<br>
|
|
||||||
it under the terms of the GNU General Public License as published by<br>
|
|
||||||
the Free Software Foundation, either version 3 of the License, or<br>
|
|
||||||
(at your option) any later version.<br>
|
|
||||||
<br>
|
|
||||||
This program is distributed in the hope that it will be useful,<br>
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of<br>
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the<br>
|
|
||||||
GNU General Public License for more details.<br>
|
|
||||||
<br>
|
|
||||||
You should have received a copy of the GNU General Public License<br>
|
|
||||||
along with this program. If not, see <<a href="http://www.gnu.org/licenses/" target="_blank">http://www.gnu.org/licenses/</a>>.<br>
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
"authentication"
|
"authentication"
|
||||||
],
|
],
|
||||||
"author": "Icy Network",
|
"author": "Icy Network",
|
||||||
"license": "GPL-3.0",
|
"license": "MIT",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/IcyNet/IcyNet.eu/issues"
|
"url": "https://github.com/IcyNet/IcyNet.eu/issues"
|
||||||
},
|
},
|
||||||
|
@ -35,9 +35,12 @@ module.exports = function () {
|
|||||||
this.logProcess = (pid, msg) => {
|
this.logProcess = (pid, msg) => {
|
||||||
if (msg.indexOf('warn') === 0) {
|
if (msg.indexOf('warn') === 0) {
|
||||||
msg = msg.substring(5)
|
msg = msg.substring(5)
|
||||||
|
console.warn('[%s] %s', pid, msg)
|
||||||
} else if (msg.indexOf('error') === 0) {
|
} else if (msg.indexOf('error') === 0) {
|
||||||
msg = msg.substring(6)
|
msg = msg.substring(6)
|
||||||
|
console.error('[%s] %s', pid, msg)
|
||||||
|
} else {
|
||||||
|
console.log('[%s] %s', pid, msg)
|
||||||
}
|
}
|
||||||
console.log('[%s] %s', pid, msg)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,6 +35,21 @@ const API = {
|
|||||||
|
|
||||||
await await models.External.query().insert(data)
|
await await models.External.query().insert(data)
|
||||||
return true
|
return true
|
||||||
|
},
|
||||||
|
remove: async (user, service) => {
|
||||||
|
user = await UAPI.User.ensureObject(user, ['password'])
|
||||||
|
let userExterns = await models.External.query().orderBy('created_at', 'asc').where('user_id', user.id)
|
||||||
|
|
||||||
|
if (!userExterns.length) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do not remove the service the user signed up with
|
||||||
|
if (userExterns[0] && (user.password === '' || user.password === null) && userExterns[0].service === service) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return models.External.query().delete().where('user_id', user.id).andWhere('service', service)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Facebook: {
|
Facebook: {
|
||||||
@ -97,12 +112,13 @@ const API = {
|
|||||||
avatar_file: profilepic,
|
avatar_file: profilepic,
|
||||||
activated: 1,
|
activated: 1,
|
||||||
ip_address: data.ip_address,
|
ip_address: data.ip_address,
|
||||||
created_at: new Date()
|
created_at: new Date(),
|
||||||
|
updated_at: new Date()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the username is already taken
|
// Check if the username is already taken
|
||||||
if (await UAPI.User.get(udataLimited.username) != null) {
|
if (await UAPI.User.get(udataLimited.username) != null) {
|
||||||
udataLimited.username = 'FB' + UAPI.Hash(4)
|
udataLimited.username = udataLimited.username + UAPI.Hash(4)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the email Facebook gave us is already registered, if so,
|
// Check if the email Facebook gave us is already registered, if so,
|
||||||
@ -209,12 +225,13 @@ const API = {
|
|||||||
avatar_file: profilepic,
|
avatar_file: profilepic,
|
||||||
activated: 1,
|
activated: 1,
|
||||||
ip_address: ipAddress,
|
ip_address: ipAddress,
|
||||||
|
updated_at: new Date(),
|
||||||
created_at: new Date()
|
created_at: new Date()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the username is already taken
|
// Check if the username is already taken
|
||||||
if (await UAPI.User.get(udataLimited.username) != null) {
|
if (await UAPI.User.get(udataLimited.username) != null) {
|
||||||
udataLimited.username = 'Tw' + UAPI.Hash(4)
|
udataLimited.username = udataLimited.username + UAPI.Hash(4)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the email Twitter gave us is already registered, if so,
|
// Check if the email Twitter gave us is already registered, if so,
|
||||||
@ -320,6 +337,7 @@ const API = {
|
|||||||
avatar_file: profilepic,
|
avatar_file: profilepic,
|
||||||
activated: 1,
|
activated: 1,
|
||||||
ip_address: ipAddress,
|
ip_address: ipAddress,
|
||||||
|
updated_at: new Date(),
|
||||||
created_at: new Date()
|
created_at: new Date()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,9 +86,42 @@ const API = {
|
|||||||
|
|
||||||
return null
|
return null
|
||||||
},
|
},
|
||||||
|
socialStatus: async function (user) {
|
||||||
|
user = await API.User.ensureObject(user, ['password'])
|
||||||
|
if (!user) return null
|
||||||
|
let external = await models.External.query().orderBy('created_at', 'asc').where('user_id', user.id)
|
||||||
|
let enabled = {}
|
||||||
|
|
||||||
|
for (let i in external) {
|
||||||
|
let ext = external[i]
|
||||||
|
enabled[ext.service] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
let accountSourceIsExternal = user.password === null || user.password === ''
|
||||||
|
let obj = {
|
||||||
|
enabled: enabled,
|
||||||
|
password: !accountSourceIsExternal
|
||||||
|
}
|
||||||
|
|
||||||
|
if (accountSourceIsExternal) {
|
||||||
|
obj.source = external[0].service
|
||||||
|
}
|
||||||
|
|
||||||
|
return obj
|
||||||
|
},
|
||||||
|
update: async function (user, data) {
|
||||||
|
user = await API.User.ensureObject(user)
|
||||||
|
if (!user) return {error: 'No such user.'}
|
||||||
|
|
||||||
|
data = Object.assign({
|
||||||
|
updated_at: new Date()
|
||||||
|
}, data)
|
||||||
|
|
||||||
|
return models.User.query().patchAndFetchById(user.id, data)
|
||||||
|
},
|
||||||
Login: {
|
Login: {
|
||||||
password: async function (user, password) {
|
password: async function (user, password) {
|
||||||
user = await API.User.ensureObject(user)
|
user = await API.User.ensureObject(user, ['password'])
|
||||||
if (!user.password) return false
|
if (!user.password) return false
|
||||||
return bcryptTask({task: 'compare', password: password, hash: user.password})
|
return bcryptTask({task: 'compare', password: password, hash: user.password})
|
||||||
},
|
},
|
||||||
@ -193,6 +226,7 @@ const API = {
|
|||||||
let email = config.email && config.email.enabled
|
let email = config.email && config.email.enabled
|
||||||
let data = Object.assign(regdata, {
|
let data = Object.assign(regdata, {
|
||||||
created_at: new Date(),
|
created_at: new Date(),
|
||||||
|
updated_at: new Date(),
|
||||||
activated: email ? 0 : 1
|
activated: email ? 0 : 1
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -62,12 +62,7 @@ function createSession (req, user) {
|
|||||||
|
|
||||||
// Either give JSON or make a redirect
|
// Either give JSON or make a redirect
|
||||||
function JsonData (req, res, error, redirect = '/') {
|
function JsonData (req, res, error, redirect = '/') {
|
||||||
if (req.headers['content-type'] === 'application/json') {
|
res.jsonp({error: error, redirect: redirect})
|
||||||
return res.jsonp({error: error, redirect: redirect})
|
|
||||||
}
|
|
||||||
|
|
||||||
req.flash('message', {error: true, text: error})
|
|
||||||
res.redirect(redirect)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** FACEBOOK LOGIN
|
/** FACEBOOK LOGIN
|
||||||
@ -94,6 +89,17 @@ router.post('/external/facebook/callback', wrap(async (req, res) => {
|
|||||||
JsonData(req, res, null, uri)
|
JsonData(req, res, null, uri)
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
router.get('/external/facebook/remove', wrap(async (req, res) => {
|
||||||
|
if (!req.session.user) return res.redirect('/login')
|
||||||
|
let done = await APIExtern.Common.remove(req.session.user, 'fb')
|
||||||
|
|
||||||
|
if (!done) {
|
||||||
|
req.flash('message', {error: true, text: 'Unable to unlink social media account'})
|
||||||
|
}
|
||||||
|
|
||||||
|
res.redirect('/user/manage')
|
||||||
|
}))
|
||||||
|
|
||||||
/** TWITTER LOGIN
|
/** TWITTER LOGIN
|
||||||
* OAuth1.0a flows
|
* OAuth1.0a flows
|
||||||
* Tokens in configs
|
* Tokens in configs
|
||||||
@ -147,6 +153,18 @@ router.get('/external/twitter/callback', wrap(async (req, res) => {
|
|||||||
res.redirect(uri)
|
res.redirect(uri)
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
router.get('/external/twitter/remove', wrap(async (req, res) => {
|
||||||
|
if (!req.session.user) return res.redirect('/login')
|
||||||
|
|
||||||
|
let done = await APIExtern.Common.remove(req.session.user, 'twitter')
|
||||||
|
|
||||||
|
if (!done) {
|
||||||
|
req.flash('message', {error: true, text: 'Unable to unlink social media account'})
|
||||||
|
}
|
||||||
|
|
||||||
|
res.redirect('/user/manage')
|
||||||
|
}))
|
||||||
|
|
||||||
/** DISCORD LOGIN
|
/** DISCORD LOGIN
|
||||||
* OAuth2 flows
|
* OAuth2 flows
|
||||||
* Tokens in configs
|
* Tokens in configs
|
||||||
@ -205,6 +223,18 @@ router.get('/external/discord/callback', wrap(async (req, res) => {
|
|||||||
res.redirect(uri)
|
res.redirect(uri)
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
router.get('/external/discord/remove', wrap(async (req, res) => {
|
||||||
|
if (!req.session.user) return res.redirect('/login')
|
||||||
|
|
||||||
|
let done = await APIExtern.Common.remove(req.session.user, 'discord')
|
||||||
|
|
||||||
|
if (!done) {
|
||||||
|
req.flash('message', {error: true, text: 'Unable to unlink social media account'})
|
||||||
|
}
|
||||||
|
|
||||||
|
res.redirect('/user/manage')
|
||||||
|
}))
|
||||||
|
|
||||||
/* ========
|
/* ========
|
||||||
* NEWS
|
* NEWS
|
||||||
* ========
|
* ========
|
||||||
@ -247,4 +277,9 @@ router.get('/news', wrap(async (req, res) => {
|
|||||||
res.jsonp(articles)
|
res.jsonp(articles)
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
// 404
|
||||||
|
router.use((req, res) => {
|
||||||
|
res.status(404).jsonp({error: 'Not found'})
|
||||||
|
})
|
||||||
|
|
||||||
module.exports = router
|
module.exports = router
|
||||||
|
@ -8,6 +8,7 @@ import wrap from '../../scripts/asyncRoute'
|
|||||||
import http from '../../scripts/http'
|
import http from '../../scripts/http'
|
||||||
import API from '../api'
|
import API from '../api'
|
||||||
import News from '../api/news'
|
import News from '../api/news'
|
||||||
|
import emailer from '../api/emailer'
|
||||||
|
|
||||||
import apiRouter from './api'
|
import apiRouter from './api'
|
||||||
import oauthRouter from './oauth2'
|
import oauthRouter from './oauth2'
|
||||||
@ -21,6 +22,17 @@ let accountLimiter = new RateLimit({
|
|||||||
message: 'Whoa, slow down there, buddy! You just hit our rate limits. Try again in 1 hour.'
|
message: 'Whoa, slow down there, buddy! You just hit our rate limits. Try again in 1 hour.'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
function setSession (req, user) {
|
||||||
|
req.session.user = {
|
||||||
|
id: user.id,
|
||||||
|
username: user.username,
|
||||||
|
display_name: user.display_name,
|
||||||
|
email: user.email,
|
||||||
|
avatar_file: user.avatar_file,
|
||||||
|
session_refresh: Date.now() + 1800000 // 30 minutes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
router.use(wrap(async (req, res, next) => {
|
router.use(wrap(async (req, res, next) => {
|
||||||
let messages = req.flash('message')
|
let messages = req.flash('message')
|
||||||
if (!messages || !messages.length) {
|
if (!messages || !messages.length) {
|
||||||
@ -29,6 +41,18 @@ router.use(wrap(async (req, res, next) => {
|
|||||||
messages = messages[0]
|
messages = messages[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update user session every 30 minutes
|
||||||
|
if (req.session.user) {
|
||||||
|
if (!req.session.user.session_refresh) {
|
||||||
|
req.session.user.session_refresh = Date.now() + 1800000
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.session.user.session_refresh < Date.now()) {
|
||||||
|
let udata = await API.User.get(req.session.user.id)
|
||||||
|
setSession(req, udata)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
res.locals.message = messages
|
res.locals.message = messages
|
||||||
next()
|
next()
|
||||||
}))
|
}))
|
||||||
@ -117,6 +141,49 @@ router.get('/login/verify', (req, res) => {
|
|||||||
res.render('totp-check')
|
res.render('totp-check')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
router.get('/user/manage', wrap(async (req, res) => {
|
||||||
|
if (!req.session.user) return res.redirect('/login')
|
||||||
|
|
||||||
|
let totpEnabled = false
|
||||||
|
let socialStatus = await API.User.socialStatus(req.session.user)
|
||||||
|
|
||||||
|
if (socialStatus.password) {
|
||||||
|
totpEnabled = await API.User.Login.totpTokenRequired(req.session.user)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.twitter && config.twitter.api) {
|
||||||
|
if (!socialStatus.enabled.twitter) {
|
||||||
|
res.locals.twitter_auth = true
|
||||||
|
} else if (!socialStatus.source && socialStatus.source !== 'twitter') {
|
||||||
|
res.locals.twitter_auth = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.discord && config.discord.api) {
|
||||||
|
if (!socialStatus.enabled.discord) {
|
||||||
|
res.locals.discord_auth = true
|
||||||
|
} else if (!socialStatus.source && socialStatus.source !== 'discord') {
|
||||||
|
res.locals.discord_auth = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.facebook && config.facebook.client) {
|
||||||
|
if (!socialStatus.enabled.fb) {
|
||||||
|
res.locals.facebook_auth = config.facebook.client
|
||||||
|
} else if (!socialStatus.source && socialStatus.source !== 'fb') {
|
||||||
|
res.locals.facebook_auth = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res.render('settings', {totp: totpEnabled, password: socialStatus.password})
|
||||||
|
}))
|
||||||
|
|
||||||
|
router.get('/user/manage/password', wrap(async (req, res) => {
|
||||||
|
if (!req.session.user) return res.redirect('/login')
|
||||||
|
|
||||||
|
res.render('password_new')
|
||||||
|
}))
|
||||||
|
|
||||||
/*
|
/*
|
||||||
=================
|
=================
|
||||||
POST HANDLING
|
POST HANDLING
|
||||||
@ -125,10 +192,9 @@ router.get('/login/verify', (req, res) => {
|
|||||||
|
|
||||||
function formError (req, res, error, redirect) {
|
function formError (req, res, error, redirect) {
|
||||||
// Security measures: never store any passwords in any session
|
// Security measures: never store any passwords in any session
|
||||||
if (req.body.password) {
|
for (let key in req.body) {
|
||||||
delete req.body.password
|
if (key.indexOf('password') !== -1) {
|
||||||
if (req.body.password_repeat) {
|
delete req.body[key]
|
||||||
delete req.body.password_repeat
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -239,14 +305,7 @@ router.post('/login', wrap(async (req, res) => {
|
|||||||
// TODO: Ban checks
|
// TODO: Ban checks
|
||||||
|
|
||||||
// Set session
|
// Set session
|
||||||
req.session.user = {
|
setSession(req, user)
|
||||||
id: user.id,
|
|
||||||
username: user.username,
|
|
||||||
display_name: user.display_name,
|
|
||||||
email: user.email,
|
|
||||||
avatar_file: user.avatar_file,
|
|
||||||
session_refresh: Date.now() + 1800000 // 30 minutes
|
|
||||||
}
|
|
||||||
|
|
||||||
let uri = '/'
|
let uri = '/'
|
||||||
if (req.session.redirectUri) {
|
if (req.session.redirectUri) {
|
||||||
@ -340,6 +399,91 @@ router.post('/register', accountLimiter, wrap(async (req, res) => {
|
|||||||
res.redirect('/login')
|
res.redirect('/login')
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
router.post('/user/manage', wrap(async (req, res, next) => {
|
||||||
|
if (!req.session.user) return next()
|
||||||
|
|
||||||
|
if (req.body.csrf !== req.session.csrf) {
|
||||||
|
return formError(req, res, 'Invalid session! Try reloading the page.')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!req.body.display_name) {
|
||||||
|
return formError(req, res, 'Display Name cannot be blank.')
|
||||||
|
}
|
||||||
|
|
||||||
|
let displayName = req.body.display_name
|
||||||
|
if (!displayName || !displayName.match(/^([^\\`]{3,32})$/i)) {
|
||||||
|
return formError(req, res, 'Invalid display name!')
|
||||||
|
}
|
||||||
|
|
||||||
|
// No change
|
||||||
|
if (displayName === req.session.user.display_name) {
|
||||||
|
return res.redirect('/user/manage')
|
||||||
|
}
|
||||||
|
|
||||||
|
let success = await API.User.update(req.session.user, {
|
||||||
|
display_name: displayName
|
||||||
|
})
|
||||||
|
|
||||||
|
if (success.error) {
|
||||||
|
return formError(req, res, success.error)
|
||||||
|
}
|
||||||
|
|
||||||
|
req.session.user.display_name = displayName
|
||||||
|
|
||||||
|
req.flash('message', {error: false, text: 'Settings changed successfully. Please note that it may take time to update on other websites and devices.'})
|
||||||
|
res.redirect('/user/manage')
|
||||||
|
}))
|
||||||
|
|
||||||
|
router.post('/user/manage/password', wrap(async (req, res, next) => {
|
||||||
|
if (!req.session.user) return next()
|
||||||
|
|
||||||
|
if (req.body.csrf !== req.session.csrf) {
|
||||||
|
return formError(req, res, 'Invalid session! Try reloading the page.')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!req.body.password_old) {
|
||||||
|
return formError(req, res, 'Please enter your current password.')
|
||||||
|
}
|
||||||
|
|
||||||
|
let passwordMatch = await API.User.Login.password(req.session.user, req.body.password_old)
|
||||||
|
if (!passwordMatch) {
|
||||||
|
return formError(req, res, 'The password you provided is incorrect.')
|
||||||
|
}
|
||||||
|
|
||||||
|
let password = req.body.password
|
||||||
|
if (!password || password.length < 8 || password.length > 32) {
|
||||||
|
return formError(req, res, 'Invalid password! Keep it between 8 and 32 characters!')
|
||||||
|
}
|
||||||
|
|
||||||
|
let passwordAgain = req.body.password_repeat
|
||||||
|
if (!passwordAgain || password !== passwordAgain) {
|
||||||
|
return formError(req, res, 'The passwords do not match!')
|
||||||
|
}
|
||||||
|
|
||||||
|
password = await API.User.Register.hashPassword(password)
|
||||||
|
|
||||||
|
let success = await API.User.update(req.session.user, {
|
||||||
|
password: password
|
||||||
|
})
|
||||||
|
|
||||||
|
if (success.error) {
|
||||||
|
return formError(req, res, success.error)
|
||||||
|
}
|
||||||
|
|
||||||
|
let user = req.session.user
|
||||||
|
console.warn('[SECURITY AUDIT] User \'%s\' password has been changed from %s', user.username, req.realIP)
|
||||||
|
|
||||||
|
if (config.email && config.email.enabled) {
|
||||||
|
await emailer.pushMail('password_alert', user.email, {
|
||||||
|
display_name: user.display_name,
|
||||||
|
ip: req.realIP
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
req.flash('message', {error: false, text: 'Password changed successfully.'})
|
||||||
|
return res.redirect('/user/manage')
|
||||||
|
}))
|
||||||
|
|
||||||
/*
|
/*
|
||||||
=============
|
=============
|
||||||
DOCUMENTS
|
DOCUMENTS
|
||||||
@ -347,10 +491,10 @@ router.post('/register', accountLimiter, wrap(async (req, res) => {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
const docsDir = path.join(__dirname, '../../documents')
|
const docsDir = path.join(__dirname, '../../documents')
|
||||||
router.get('/docs/:name', (req, res) => {
|
router.get('/docs/:name', (req, res, next) => {
|
||||||
let doc = path.join(docsDir, req.params.name + '.html')
|
let doc = path.join(docsDir, req.params.name + '.html')
|
||||||
if (!fs.existsSync(docsDir) || !fs.existsSync(doc)) {
|
if (!fs.existsSync(docsDir) || !fs.existsSync(doc)) {
|
||||||
return res.status(404).end()
|
return next()
|
||||||
}
|
}
|
||||||
|
|
||||||
doc = fs.readFileSync(doc, {encoding: 'utf8'})
|
doc = fs.readFileSync(doc, {encoding: 'utf8'})
|
||||||
@ -410,6 +554,10 @@ router.get('/activate/:token', wrap(async (req, res) => {
|
|||||||
|
|
||||||
router.use('/api', apiRouter)
|
router.use('/api', apiRouter)
|
||||||
|
|
||||||
|
router.use((req, res) => {
|
||||||
|
res.status(404).render('404')
|
||||||
|
})
|
||||||
|
|
||||||
router.use((err, req, res, next) => {
|
router.use((err, req, res, next) => {
|
||||||
console.error(err)
|
console.error(err)
|
||||||
next()
|
next()
|
||||||
|
@ -49,12 +49,12 @@ router.get('/user', oauth.bearer, wrap(async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Include Email
|
// Include Email
|
||||||
if (accessToken.scope.indexOf('email') != -1) {
|
if (accessToken.scope.indexOf('email') !== -1) {
|
||||||
udata.email = user.email
|
udata.email = user.email
|
||||||
}
|
}
|
||||||
|
|
||||||
// Include privilege number
|
// Include privilege number
|
||||||
if (accessToken.scope.indexOf('privilege') != -1) {
|
if (accessToken.scope.indexOf('privilege') !== -1) {
|
||||||
udata.privilege = user.nw_privilege
|
udata.privilege = user.nw_privilege
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,6 +109,7 @@ $(document).ready(function () {
|
|||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
data: response,
|
data: response,
|
||||||
success: function (data) {
|
success: function (data) {
|
||||||
|
console.log(data)
|
||||||
if (data.error) {
|
if (data.error) {
|
||||||
$('.message').addClass('error')
|
$('.message').addClass('error')
|
||||||
$('.message span').text(data.error)
|
$('.message span').text(data.error)
|
||||||
|
@ -231,6 +231,14 @@ input:not([type="submit"])
|
|||||||
|
|
||||||
.boxcont
|
.boxcont
|
||||||
.box
|
.box
|
||||||
|
padding: 20px
|
||||||
|
margin: auto
|
||||||
|
margin-top: 5%
|
||||||
|
background-color: #fff
|
||||||
|
box-shadow: 5px 5px 15px #868686
|
||||||
|
border: 1px solid #ddd
|
||||||
|
h1:first-child, h2:first-child, h3:first-child
|
||||||
|
margin-top: 0
|
||||||
.left, .right
|
.left, .right
|
||||||
display: inline-block
|
display: inline-block
|
||||||
width: 50%
|
width: 50%
|
||||||
@ -241,16 +249,13 @@ input:not([type="submit"])
|
|||||||
width: 46%
|
width: 46%
|
||||||
&#login
|
&#login
|
||||||
max-width: 700px
|
max-width: 700px
|
||||||
|
&#settings
|
||||||
|
max-width: 700px
|
||||||
|
min-height: 380px
|
||||||
&#totpcheck
|
&#totpcheck
|
||||||
max-width: 400px
|
max-width: 400px
|
||||||
padding: 20px
|
&#error
|
||||||
margin: auto
|
width: 200px
|
||||||
margin-top: 5%
|
|
||||||
background-color: #fff
|
|
||||||
box-shadow: 5px 5px 15px #868686
|
|
||||||
border: 1px solid #ddd
|
|
||||||
h1, h2, h3
|
|
||||||
margin-top: 0
|
|
||||||
|
|
||||||
.pgn
|
.pgn
|
||||||
display: inline-block
|
display: inline-block
|
||||||
@ -423,6 +428,9 @@ input.invalid
|
|||||||
border-color: #ffffff
|
border-color: #ffffff
|
||||||
margin: 20px 0
|
margin: 20px 0
|
||||||
|
|
||||||
|
.option
|
||||||
|
display: block
|
||||||
|
|
||||||
@media all and (max-width: 800px)
|
@media all and (max-width: 800px)
|
||||||
.navigator
|
.navigator
|
||||||
padding: 0 10px
|
padding: 0 10px
|
||||||
|
@ -3,3 +3,4 @@ p Before you can log in, you must activate your account.
|
|||||||
p Click on or copy the following link into your URL bar in order to activate your Icy Network account
|
p Click on or copy the following link into your URL bar in order to activate your Icy Network account
|
||||||
a.activate(href=domain + "/activate/" + activation_token, target="_blank", rel="nofollow")= domain + "/activate/" + activation_token
|
a.activate(href=domain + "/activate/" + activation_token, target="_blank", rel="nofollow")= domain + "/activate/" + activation_token
|
||||||
p If you did not register for an account on Icy Network, please ignore this email.
|
p If you did not register for an account on Icy Network, please ignore this email.
|
||||||
|
small This email has been sent to you because of an action performed on the IcyNet.eu website.
|
||||||
|
6
templates/password_alert/html.pug
Normal file
6
templates/password_alert/html.pug
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
h3 Hello, #{display_name}!
|
||||||
|
p This email was sent to you because your password on Icy Network has been changed.
|
||||||
|
p If you did indeed change the password of this account yourself, you may safely ignore this email.
|
||||||
|
p
|
||||||
|
b However, if you did not change your password on Icy Network, contact an Icy Network administrator immediately!
|
||||||
|
small This email has been sent to you because of an action performed on the IcyNet.eu website.
|
1
templates/password_alert/subject.pug
Normal file
1
templates/password_alert/subject.pug
Normal file
@ -0,0 +1 @@
|
|||||||
|
|Your Password on Icy Network has been changed!
|
12
views/404.pug
Normal file
12
views/404.pug
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
extends layout.pug
|
||||||
|
block title
|
||||||
|
|Icy Network - 404
|
||||||
|
|
||||||
|
block body
|
||||||
|
.wrapper
|
||||||
|
.boxcont
|
||||||
|
.box#error
|
||||||
|
h1 404
|
||||||
|
p Page not found
|
||||||
|
a(href="..") Go back
|
||||||
|
|
@ -1,5 +1,8 @@
|
|||||||
extends layout.pug
|
extends layout.pug
|
||||||
|
|
||||||
|
block title
|
||||||
|
|Icy Network - Legal Notice
|
||||||
|
|
||||||
block body
|
block body
|
||||||
.document
|
.document
|
||||||
.content !{doc}
|
.content !{doc}
|
||||||
|
28
views/password_new.pug
Normal file
28
views/password_new.pug
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
extends layout.pug
|
||||||
|
block title
|
||||||
|
|Icy Network - Change User Password
|
||||||
|
|
||||||
|
block body
|
||||||
|
.wrapper
|
||||||
|
.boxcont
|
||||||
|
.box#totpcheck
|
||||||
|
h1 Change Your Password
|
||||||
|
if message
|
||||||
|
if message.error
|
||||||
|
.message.error
|
||||||
|
else
|
||||||
|
.message
|
||||||
|
span #{message.text}
|
||||||
|
form#loginForm(method="POST", action="")
|
||||||
|
input(type="hidden", name="csrf", value=csrf)
|
||||||
|
if !token
|
||||||
|
label(for="password_old") Current Password
|
||||||
|
input(type="password", name="password_old", id="password_old")
|
||||||
|
else
|
||||||
|
input(type="hidden", name="token", value=token)
|
||||||
|
label(for="password") New Password
|
||||||
|
input(type="password", name="password", id="password")
|
||||||
|
label(for="password_repeat") Repeat New Password
|
||||||
|
input(type="password", name="password_repeat", id="password_repeat")
|
||||||
|
div#repeatcheck(style="display: none")
|
||||||
|
input(type="submit", value="Change")
|
48
views/settings.pug
Normal file
48
views/settings.pug
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
extends layout.pug
|
||||||
|
block title
|
||||||
|
|Icy Network - User Settings
|
||||||
|
|
||||||
|
block body
|
||||||
|
.wrapper
|
||||||
|
.boxcont
|
||||||
|
.box#settings
|
||||||
|
h1 User Settings
|
||||||
|
.left
|
||||||
|
if message
|
||||||
|
if message.error
|
||||||
|
.message.error
|
||||||
|
else
|
||||||
|
.message
|
||||||
|
span #{message.text}
|
||||||
|
form#loginForm(method="POST", action="")
|
||||||
|
input(type="hidden", name="csrf", value=csrf)
|
||||||
|
label(for="username") Username
|
||||||
|
input(type="text", name="username", id="username", value=user.username, disabled)
|
||||||
|
label(for="display_name") Display Name
|
||||||
|
input(type="text", name="display_name", id="display_name", value=user.display_name)
|
||||||
|
input(type="submit", value="Save Settings")
|
||||||
|
.right
|
||||||
|
h3 Social Media Accounts
|
||||||
|
include includes/external.pug
|
||||||
|
if twitter_auth == false
|
||||||
|
a.option(href="/api/external/twitter/remove") Unlink Twitter
|
||||||
|
if facebook_auth == false
|
||||||
|
a.option(href="/api/external/facebook/remove") Unlink Facebook
|
||||||
|
if discord_auth == false
|
||||||
|
a.option(href="/api/external/discord/remove") Unlink Discord
|
||||||
|
h3 Other Options
|
||||||
|
if password
|
||||||
|
a.option(href="/user/manage/password")
|
||||||
|
i.fa.fa-fw.fa-lock
|
||||||
|
|Change Password
|
||||||
|
if totp
|
||||||
|
a.option(href="/user/two-factor/disable")
|
||||||
|
i.fa.fa-fw.fa-lock
|
||||||
|
|Disable Two-Factor Authentication
|
||||||
|
else
|
||||||
|
a.option(href="/user/two-factor")
|
||||||
|
i.fa.fa-fw.fa-lock
|
||||||
|
|Enable Two-Factor Authentication
|
||||||
|
a.option(href="/user/manage/email")
|
||||||
|
i.fa.fa-fw.fa-envelope
|
||||||
|
|Change Email Address
|
Reference in New Issue
Block a user