Major refactor and code modernization, vol. 1
This commit is contained in:
parent
229a7db04a
commit
444f8b00f8
7378
package-lock.json
generated
7378
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
59
package.json
59
package.json
@ -30,59 +30,60 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://icynet.eu",
|
"homepage": "https://icynet.eu",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/plugin-transform-modules-commonjs": "^7.10.4",
|
"@babel/plugin-transform-modules-commonjs": "^7.12.1",
|
||||||
"@babel/register": "^7.10.4",
|
"@babel/register": "^7.12.10",
|
||||||
"bcryptjs": "^2.4.3",
|
"bcryptjs": "^2.4.3",
|
||||||
"bluebird": "^3.7.2",
|
"bluebird": "^3.7.2",
|
||||||
"body-parser": "^1.19.0",
|
"body-parser": "^1.19.0",
|
||||||
"connect-redis": "^4.0.4",
|
"connect-redis": "^5.0.0",
|
||||||
"connect-session-knex": "^1.7.3",
|
"connect-session-knex": "^2.0.0",
|
||||||
"email-templates": "^7.0.5",
|
"email-templates": "^8.0.2",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"express-rate-limit": "^5.1.3",
|
"express-rate-limit": "^5.2.3",
|
||||||
"express-session": "^1.17.1",
|
"express-session": "^1.17.1",
|
||||||
"feed": "^4.2.1",
|
"feed": "^4.2.1",
|
||||||
"fs-extra": "^9.0.1",
|
"fs-extra": "^9.0.1",
|
||||||
"gm": "^1.23.1",
|
"gm": "^1.23.1",
|
||||||
"knex": "^0.21.1",
|
"knex": "^0.21.13",
|
||||||
"multiparty": "^4.2.1",
|
"multiparty": "^4.2.2",
|
||||||
"mysql": "^2.18.1",
|
"mysql": "^2.18.1",
|
||||||
"nodemailer": "^6.4.10",
|
"nodemailer": "^6.4.17",
|
||||||
"notp": "^2.0.3",
|
"notp": "^2.0.3",
|
||||||
"oauth-libre": "^0.9.17",
|
"oauth-libre": "^0.9.17",
|
||||||
"objection": "^2.2.1",
|
"objection": "^2.2.3",
|
||||||
|
"querystring-es3": "^0.2.1",
|
||||||
"redis": "^3.0.2",
|
"redis": "^3.0.2",
|
||||||
"serve-favicon": "^2.5.0",
|
"serve-favicon": "^2.5.0",
|
||||||
"stylus": "^0.54.7",
|
"stylus": "^0.54.8",
|
||||||
"thirty-two": "^1.0.2",
|
"thirty-two": "^1.0.2",
|
||||||
"toml": "^3.0.0",
|
"toml": "^3.0.0",
|
||||||
"uuid": "^8.2.0",
|
"uuid": "^8.3.2",
|
||||||
"vue": "^2.6.11"
|
"vue": "^2.6.12"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.10.4",
|
"@babel/core": "^7.12.10",
|
||||||
"@babel/preset-env": "^7.10.4",
|
"@babel/preset-env": "^7.12.10",
|
||||||
"babel-loader": "^8.1.0",
|
"babel-loader": "^8.2.2",
|
||||||
"bootstrap": "^4.5.0",
|
"bootstrap": "^4.5.3",
|
||||||
"concurrently": "^5.2.0",
|
"concurrently": "^5.3.0",
|
||||||
"eslint-plugin-import": "^2.22.0",
|
"eslint-plugin-import": "^2.22.1",
|
||||||
"jquery": "^3.5.1",
|
"jquery": "^3.5.1",
|
||||||
"morgan": "^1.10.0",
|
"morgan": "^1.10.0",
|
||||||
"mustache": "^4.0.1",
|
"mustache": "^4.1.0",
|
||||||
"popper.js": "^1.16.1",
|
"popper.js": "^1.16.1",
|
||||||
"pug": "^3.0.0",
|
"pug": "^3.0.0",
|
||||||
"pug-plain-loader": "^1.0.0",
|
"pug-plain-loader": "^1.1.0",
|
||||||
"standard": "^14.3.4",
|
"standard": "^16.0.3",
|
||||||
"terser-webpack-plugin": "^3.0.6",
|
"terser-webpack-plugin": "^5.0.3",
|
||||||
"vue-clickaway": "^2.2.2",
|
"vue-clickaway": "^2.2.2",
|
||||||
"vue-loader": "^15.9.3",
|
"vue-loader": "^15.9.5",
|
||||||
"vue-resource": "^1.5.1",
|
"vue-resource": "^1.5.1",
|
||||||
"vue-router": "^3.3.4",
|
"vue-router": "^3.4.9",
|
||||||
"vue-template-compiler": "^2.6.11",
|
"vue-template-compiler": "^2.6.12",
|
||||||
"watch": "^1.0.2",
|
"watch": "^1.0.2",
|
||||||
"webpack": "^4.43.0",
|
"webpack": "^5.10.1",
|
||||||
"webpack-cli": "^3.3.12",
|
"webpack-cli": "^4.2.0",
|
||||||
"webpack-merge": "^5.0.7"
|
"webpack-merge": "^5.7.0"
|
||||||
},
|
},
|
||||||
"standard": {
|
"standard": {
|
||||||
"env": {
|
"env": {
|
||||||
|
@ -1 +1 @@
|
|||||||
module.exports = fn => (...args) => fn(...args).catch(args[2])
|
export default fn => (...args) => fn(...args).catch(args[2])
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
// Make sure the user is logged in
|
// Make sure the user is logged in
|
||||||
// Redirect to login page and store the current path in the session for redirecting later
|
// Redirect to login page and store the current path in the session for redirecting later
|
||||||
function ensureLogin (req, res, next) {
|
export function ensureLogin (req, res, next) {
|
||||||
if (req.session.user) return next()
|
if (req.session.user) return next()
|
||||||
req.session.redirectUri = req.originalUrl
|
req.session.redirectUri = req.originalUrl
|
||||||
res.redirect('/login')
|
res.redirect('/login')
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = ensureLogin
|
|
||||||
|
@ -6,7 +6,7 @@ const format = util.format
|
|||||||
* Included here to avoid includng ridiculously small modules
|
* Included here to avoid includng ridiculously small modules
|
||||||
*/
|
*/
|
||||||
|
|
||||||
module.exports = function (options) {
|
export default function (options) {
|
||||||
options = options || {}
|
options = options || {}
|
||||||
const safe = (options.unsafe === undefined) ? true : !options.unsafe
|
const safe = (options.unsafe === undefined) ? true : !options.unsafe
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ import { URL } from 'url'
|
|||||||
import qs from 'querystring'
|
import qs from 'querystring'
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
|
|
||||||
function HTTP_GET (link, headers = {}, lback) {
|
export function httpGET (link, headers = {}, lback) {
|
||||||
if (lback && lback >= 4) throw new Error('infinite loop!') // Prevent infinite loop requests
|
if (lback && lback >= 4) throw new Error('infinite loop!') // Prevent infinite loop requests
|
||||||
const parsed = new URL(link)
|
const parsed = new URL(link)
|
||||||
const opts = {
|
const opts = {
|
||||||
@ -32,7 +32,7 @@ function HTTP_GET (link, headers = {}, lback) {
|
|||||||
lback += 1
|
lback += 1
|
||||||
}
|
}
|
||||||
|
|
||||||
return HTTP_GET(res.headers.location, headers, lback).then(resolve, reject)
|
return httpGET(res.headers.location, headers, lback).then(resolve, reject)
|
||||||
}
|
}
|
||||||
|
|
||||||
let data = ''
|
let data = ''
|
||||||
@ -60,7 +60,7 @@ function HTTP_GET (link, headers = {}, lback) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function HTTP_POST (link, headers = {}, data) {
|
export function httpPOST (link, headers = {}, data) {
|
||||||
const parsed = new URL(link)
|
const parsed = new URL(link)
|
||||||
let postData = qs.stringify(data)
|
let postData = qs.stringify(data)
|
||||||
|
|
||||||
@ -106,7 +106,7 @@ function HTTP_POST (link, headers = {}, data) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async function Download (url, dest) {
|
export async function downloadURL (url, dest) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const file = fs.createWriteStream(dest)
|
const file = fs.createWriteStream(dest)
|
||||||
const protocol = url.indexOf('https:') === 0 ? require('https') : require('http')
|
const protocol = url.indexOf('https:') === 0 ? require('https') : require('http')
|
||||||
@ -121,9 +121,3 @@ async function Download (url, dest) {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
GET: HTTP_GET,
|
|
||||||
POST: HTTP_POST,
|
|
||||||
Download: Download
|
|
||||||
}
|
|
||||||
|
@ -67,10 +67,8 @@ async function initializeLogger () {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
export function initialize () {
|
||||||
initialize: function () {
|
|
||||||
// Create log file write stream
|
// Create log file write stream
|
||||||
if (!config.logger || !config.logger.write) return
|
if (!config.logger || !config.logger.write) return
|
||||||
initializeLogger().catch((e) => console.error(e))
|
initializeLogger().catch((e) => console.error(e))
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import Users from './index'
|
import { User, Pagination, Hash, Login, Reset, Register } from './index'
|
||||||
import Models from './models'
|
import * as Models from './models'
|
||||||
|
|
||||||
const perPage = 6
|
const perPage = 6
|
||||||
|
|
||||||
async function cleanUserObject (dbe, admin) {
|
async function cleanUserObject (dbe, admin) {
|
||||||
const totp = await Users.User.Login.totpTokenRequired(dbe)
|
const totp = await Login.totpTokenRequired(dbe)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: dbe.id,
|
id: dbe.id,
|
||||||
@ -25,7 +25,7 @@ async function cleanUserObject (dbe, admin) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function cleanClientObject (dbe) {
|
async function cleanClientObject (dbe) {
|
||||||
const user = await Users.User.get(dbe.user_id)
|
const user = await User.get(dbe.user_id)
|
||||||
return {
|
return {
|
||||||
id: dbe.id,
|
id: dbe.id,
|
||||||
title: dbe.title,
|
title: dbe.title,
|
||||||
@ -46,8 +46,8 @@ async function cleanClientObject (dbe) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function cleanBanObject (dbe) {
|
async function cleanBanObject (dbe) {
|
||||||
const user = await Users.User.get(dbe.user_id)
|
const user = await User.get(dbe.user_id)
|
||||||
const admin = await Users.User.get(dbe.admin_id)
|
const admin = await User.get(dbe.admin_id)
|
||||||
return {
|
return {
|
||||||
id: dbe.id,
|
id: dbe.id,
|
||||||
reason: dbe.reason,
|
reason: dbe.reason,
|
||||||
@ -82,18 +82,17 @@ function dataFilter (data, fields, optional = []) {
|
|||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
const API = {
|
// List all users (paginated)
|
||||||
// List all users (paginated)
|
export async function getAllUsers (page, adminId) {
|
||||||
getAllUsers: async function (page, adminId) {
|
|
||||||
let count = await Models.User.query().count('id as ids')
|
let count = await Models.User.query().count('id as ids')
|
||||||
if (!count.length || !count[0].ids || isNaN(page)) {
|
if (!count.length || !count[0].ids || isNaN(page)) {
|
||||||
return { error: 'No users found in database' }
|
return { error: 'No users found in database' }
|
||||||
}
|
}
|
||||||
|
|
||||||
count = count[0].ids
|
count = count[0].ids
|
||||||
const paginated = Users.Pagination(perPage, parseInt(count), page)
|
const paginated = Pagination(perPage, parseInt(count), page)
|
||||||
const raw = await Models.User.query().offset(paginated.offset).limit(perPage)
|
const raw = await Models.User.query().offset(paginated.offset).limit(perPage)
|
||||||
const admin = await Users.User.get(adminId)
|
const admin = await User.get(adminId)
|
||||||
|
|
||||||
const users = []
|
const users = []
|
||||||
for (const i in raw) {
|
for (const i in raw) {
|
||||||
@ -106,15 +105,17 @@ const API = {
|
|||||||
page: paginated,
|
page: paginated,
|
||||||
users: users
|
users: users
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
getUser: async function (id) {
|
|
||||||
const user = await Users.User.get(id)
|
export async function getUser (id) {
|
||||||
|
const user = await User.get(id)
|
||||||
if (!user) throw new Error('No such user')
|
if (!user) throw new Error('No such user')
|
||||||
|
|
||||||
return cleanUserObject(user, null)
|
return cleanUserObject(user, null)
|
||||||
},
|
}
|
||||||
editUser: async function (id, data) {
|
|
||||||
const user = await Users.User.get(id)
|
export async function editUser (id, data) {
|
||||||
|
const user = await User.get(id)
|
||||||
if (!user) throw new Error('No such user')
|
if (!user) throw new Error('No such user')
|
||||||
|
|
||||||
const fields = [
|
const fields = [
|
||||||
@ -124,38 +125,42 @@ const API = {
|
|||||||
data = dataFilter(data, fields, ['nw_privilege', 'activated'])
|
data = dataFilter(data, fields, ['nw_privilege', 'activated'])
|
||||||
if (!data) throw new Error('Missing fields')
|
if (!data) throw new Error('Missing fields')
|
||||||
|
|
||||||
await Users.User.update(user, data)
|
await User.update(user, data)
|
||||||
|
|
||||||
return {}
|
return {}
|
||||||
},
|
}
|
||||||
resendActivationEmail: async function (id) {
|
|
||||||
const user = await Users.User.get(id)
|
export async function resendActivationEmail (id) {
|
||||||
|
const user = await User.get(id)
|
||||||
if (!user) throw new Error('No such user')
|
if (!user) throw new Error('No such user')
|
||||||
|
|
||||||
if (user.activated === 1) return {}
|
if (user.activated === 1) return {}
|
||||||
|
|
||||||
await Users.User.Register.activationEmail(user)
|
await Register.activationEmail(user)
|
||||||
|
|
||||||
return {}
|
return {}
|
||||||
},
|
}
|
||||||
revokeTotpToken: async function (id) {
|
|
||||||
const user = await Users.User.get(id)
|
export async function revokeTotpToken (id) {
|
||||||
|
const user = await User.get(id)
|
||||||
if (!user) throw new Error('No such user')
|
if (!user) throw new Error('No such user')
|
||||||
|
|
||||||
await Models.TotpToken.query().delete().where('user_id', user.id)
|
await Models.TotpToken.query().delete().where('user_id', user.id)
|
||||||
|
|
||||||
return {}
|
return {}
|
||||||
},
|
}
|
||||||
sendPasswordEmail: async function (id) {
|
|
||||||
const user = await Users.User.get(id)
|
export async function sendPasswordEmail (id) {
|
||||||
|
const user = await User.get(id)
|
||||||
if (!user) throw new Error('No such user')
|
if (!user) throw new Error('No such user')
|
||||||
|
|
||||||
const token = await Users.User.Reset.reset(user.email, false, true)
|
const token = await Reset.reset(user.email, false, true)
|
||||||
|
|
||||||
return { token }
|
return { token }
|
||||||
},
|
}
|
||||||
// Search for users by terms and fields
|
|
||||||
searchUsers: async function (terms, fields = ['email']) {
|
// Search for users by terms and fields
|
||||||
|
export async function searchUsers (terms, fields = ['email']) {
|
||||||
let qb = Models.User.query()
|
let qb = Models.User.query()
|
||||||
|
|
||||||
terms = terms.replace(/_/g, '\\_').replace(/%/g, '\\%')
|
terms = terms.replace(/_/g, '\\_').replace(/%/g, '\\%')
|
||||||
@ -177,16 +182,17 @@ const API = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return cleaned
|
return cleaned
|
||||||
},
|
}
|
||||||
// List all clients (paginated)
|
|
||||||
getAllClients: async function (page) {
|
// List all clients (paginated)
|
||||||
|
export async function getAllClients (page) {
|
||||||
let count = await Models.OAuth2Client.query().count('id as ids')
|
let count = await Models.OAuth2Client.query().count('id as ids')
|
||||||
if (!count.length || !count[0].ids || isNaN(page)) {
|
if (!count.length || !count[0].ids || isNaN(page)) {
|
||||||
return { error: 'No clients' }
|
return { error: 'No clients' }
|
||||||
}
|
}
|
||||||
|
|
||||||
count = count[0].ids
|
count = count[0].ids
|
||||||
const paginated = Users.Pagination(perPage, parseInt(count), page)
|
const paginated = Pagination(perPage, parseInt(count), page)
|
||||||
const raw = await Models.OAuth2Client.query().offset(paginated.offset).limit(perPage)
|
const raw = await Models.OAuth2Client.query().offset(paginated.offset).limit(perPage)
|
||||||
|
|
||||||
const clients = []
|
const clients = []
|
||||||
@ -199,16 +205,18 @@ const API = {
|
|||||||
return {
|
return {
|
||||||
page: paginated, clients
|
page: paginated, clients
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
// Get information about a client via id
|
|
||||||
getClient: async function (id) {
|
// Get information about a client via id
|
||||||
|
export async function getClient (id) {
|
||||||
const raw = await Models.OAuth2Client.query().where('id', id)
|
const raw = await Models.OAuth2Client.query().where('id', id)
|
||||||
if (!raw.length) throw new Error('No such client')
|
if (!raw.length) throw new Error('No such client')
|
||||||
|
|
||||||
return cleanClientObject(raw[0])
|
return cleanClientObject(raw[0])
|
||||||
},
|
}
|
||||||
// Update a client `id` in database with `data`
|
|
||||||
updateClient: async function (id, data) {
|
// Update a client `id` in database with `data`
|
||||||
|
export async function updateClient (id, data) {
|
||||||
const fields = [
|
const fields = [
|
||||||
'title', 'description', 'url', 'redirect_url', 'scope', 'verified'
|
'title', 'description', 'url', 'redirect_url', 'scope', 'verified'
|
||||||
]
|
]
|
||||||
@ -224,11 +232,12 @@ const API = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {}
|
return {}
|
||||||
},
|
}
|
||||||
// Create a new secret for a client
|
|
||||||
newSecret: async function (id) {
|
// Create a new secret for a client
|
||||||
|
export async function newSecret (id) {
|
||||||
if (isNaN(id)) throw new Error('Invalid client ID')
|
if (isNaN(id)) throw new Error('Invalid client ID')
|
||||||
const secret = Users.Hash(16)
|
const secret = Hash(16)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await Models.OAuth2Client.query().patchAndFetchById(id, { secret: secret })
|
await Models.OAuth2Client.query().patchAndFetchById(id, { secret: secret })
|
||||||
@ -237,9 +246,10 @@ const API = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {}
|
return {}
|
||||||
},
|
}
|
||||||
// Create a new client
|
|
||||||
createClient: async function (data, user) {
|
// Create a new client
|
||||||
|
export async function createClient (data, user) {
|
||||||
const fields = [
|
const fields = [
|
||||||
'title', 'description', 'url', 'redirect_url', 'scope'
|
'title', 'description', 'url', 'redirect_url', 'scope'
|
||||||
]
|
]
|
||||||
@ -248,32 +258,34 @@ const API = {
|
|||||||
if (!data) throw new Error('Missing fields')
|
if (!data) throw new Error('Missing fields')
|
||||||
|
|
||||||
const obj = Object.assign({
|
const obj = Object.assign({
|
||||||
secret: Users.Hash(16),
|
secret: Hash(16),
|
||||||
grants: 'authorization_code',
|
grants: 'authorization_code',
|
||||||
created_at: new Date(),
|
created_at: new Date(),
|
||||||
user_id: user.id
|
user_id: user.id
|
||||||
}, data)
|
}, data)
|
||||||
|
|
||||||
return Models.OAuth2Client.query().insert(obj)
|
return Models.OAuth2Client.query().insert(obj)
|
||||||
},
|
}
|
||||||
// Remove a client and all associated data
|
|
||||||
removeClient: async function (id) {
|
// Remove a client and all associated data
|
||||||
|
export async function removeClient (id) {
|
||||||
if (isNaN(id)) throw new Error('Invalid ID number')
|
if (isNaN(id)) throw new Error('Invalid ID number')
|
||||||
await Models.OAuth2Client.query().delete().where('id', id)
|
await Models.OAuth2Client.query().delete().where('id', id)
|
||||||
await Models.OAuth2AuthorizedClient.query().delete().where('client_id', id)
|
await Models.OAuth2AuthorizedClient.query().delete().where('client_id', id)
|
||||||
await Models.OAuth2AccessToken.query().delete().where('client_id', id)
|
await Models.OAuth2AccessToken.query().delete().where('client_id', id)
|
||||||
await Models.OAuth2RefreshToken.query().delete().where('client_id', id)
|
await Models.OAuth2RefreshToken.query().delete().where('client_id', id)
|
||||||
return true
|
return true
|
||||||
},
|
}
|
||||||
// List all bans (paginated)
|
|
||||||
getAllBans: async function (page) {
|
// List all bans (paginated)
|
||||||
|
export async function getAllBans (page) {
|
||||||
let count = await Models.Ban.query().count('id as ids')
|
let count = await Models.Ban.query().count('id as ids')
|
||||||
if (!count.length || !count[0].ids || isNaN(page)) {
|
if (!count.length || !count[0].ids || isNaN(page)) {
|
||||||
return { error: 'No bans on record' }
|
return { error: 'No bans on record' }
|
||||||
}
|
}
|
||||||
|
|
||||||
count = count[0].ids
|
count = count[0].ids
|
||||||
const paginated = Users.Pagination(perPage, parseInt(count), page)
|
const paginated = Pagination(perPage, parseInt(count), page)
|
||||||
const raw = await Models.Ban.query().offset(paginated.offset).limit(perPage)
|
const raw = await Models.Ban.query().offset(paginated.offset).limit(perPage)
|
||||||
|
|
||||||
const bans = []
|
const bans = []
|
||||||
@ -286,19 +298,21 @@ const API = {
|
|||||||
return {
|
return {
|
||||||
page: paginated, bans
|
page: paginated, bans
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
// Remove a ban
|
|
||||||
removeBan: async function (banId) {
|
// Remove a ban
|
||||||
|
export async function removeBan (banId) {
|
||||||
return Models.Ban.query().delete().where('id', banId)
|
return Models.Ban.query().delete().where('id', banId)
|
||||||
},
|
}
|
||||||
// Create a ban
|
|
||||||
addBan: async function (data, adminId) {
|
// Create a ban
|
||||||
const user = await Users.User.get(parseInt(data.user_id))
|
export async function addBan (data, adminId) {
|
||||||
|
const user = await User.get(parseInt(data.user_id))
|
||||||
|
|
||||||
if (!user) throw new Error('No such user.')
|
if (!user) throw new Error('No such user.')
|
||||||
if (user.id === adminId) throw new Error('Cannot ban yourself!')
|
if (user.id === adminId) throw new Error('Cannot ban yourself!')
|
||||||
|
|
||||||
const admin = await Users.User.get(adminId)
|
const admin = await User.get(adminId)
|
||||||
|
|
||||||
if (user.nw_privilege > admin.nw_privilege) throw new Error('Cannot ban user.')
|
if (user.nw_privilege > admin.nw_privilege) throw new Error('Cannot ban user.')
|
||||||
|
|
||||||
@ -313,14 +327,15 @@ const API = {
|
|||||||
|
|
||||||
await Models.Ban.query().insert(banAdd)
|
await Models.Ban.query().insert(banAdd)
|
||||||
return {}
|
return {}
|
||||||
},
|
}
|
||||||
lockAccount: async function (userId) {
|
|
||||||
const user = await Users.User.get(userId)
|
export async function lockAccount (userId) {
|
||||||
|
const user = await User.get(userId)
|
||||||
if (user.id === 1 || user.nw_privilege > 2) {
|
if (user.id === 1 || user.nw_privilege > 2) {
|
||||||
throw new Error('Cannot lock this user.')
|
throw new Error('Cannot lock this user.')
|
||||||
}
|
}
|
||||||
|
|
||||||
const lockId = Users.Hash(4)
|
const lockId = Hash(4)
|
||||||
const userObf = {
|
const userObf = {
|
||||||
username: lockId,
|
username: lockId,
|
||||||
display_name: user.username,
|
display_name: user.username,
|
||||||
@ -331,8 +346,5 @@ const API = {
|
|||||||
avatar_file: null
|
avatar_file: null
|
||||||
}
|
}
|
||||||
|
|
||||||
return Users.User.update(user, userObf)
|
return User.update(user, userObf)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = API
|
|
||||||
|
@ -16,7 +16,7 @@ const email = new Email({
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Send an email to `email` with `headers`
|
// Send an email to `email` with `headers`
|
||||||
async function sendMail (address, template, context) {
|
export async function sendMail (address, template, context) {
|
||||||
if (!email.transport) throw new Error('No transporter present!')
|
if (!email.transport) throw new Error('No transporter present!')
|
||||||
|
|
||||||
return email.send({
|
return email.send({
|
||||||
@ -29,14 +29,14 @@ async function sendMail (address, template, context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Send an email to `email` using `template` rendered with variables from `context`
|
// Send an email to `email` using `template` rendered with variables from `context`
|
||||||
async function pushMail (template, address, context) {
|
export async function pushMail (template, address, context) {
|
||||||
console.debug('Mail being sent: %s to %s', template, email)
|
console.debug('Mail being sent: %s to %s', template, email)
|
||||||
|
|
||||||
return sendMail(address, template, context)
|
return sendMail(address, template, context)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transporter initialization
|
// Transporter initialization
|
||||||
async function init () {
|
export async function init () {
|
||||||
if (!config.email || config.email.enabled === false) return
|
if (!config.email || config.email.enabled === false) return
|
||||||
const transporter = nodemailer.createTransport(config.email.transport)
|
const transporter = nodemailer.createTransport(config.email.transport)
|
||||||
|
|
||||||
@ -53,8 +53,3 @@ async function init () {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
sendMail: sendMail,
|
|
||||||
pushMail: pushMail,
|
|
||||||
init: init
|
|
||||||
}
|
|
||||||
|
@ -4,46 +4,48 @@ import { v1 as uuidV1 } from 'uuid'
|
|||||||
import crypto from 'crypto'
|
import crypto from 'crypto'
|
||||||
|
|
||||||
import config from '../../scripts/load-config'
|
import config from '../../scripts/load-config'
|
||||||
import http from '../../scripts/http'
|
import { httpGET } from '../../scripts/http'
|
||||||
import models from './models'
|
import * as models from './models'
|
||||||
import Image from './image'
|
import Image from './image'
|
||||||
import UAPI from './index'
|
import { User, Hash } from './index'
|
||||||
|
|
||||||
const userFields = ['username', 'email', 'avatar_file', 'display_name', 'ip_address']
|
const userFields = ['username', 'email', 'avatar_file', 'display_name', 'ip_address']
|
||||||
|
|
||||||
let twitterApp
|
let twitterApp
|
||||||
let discordApp
|
let discordApp
|
||||||
|
|
||||||
const API = {
|
export class Common {
|
||||||
Common: {
|
|
||||||
// Generate a hash based on the current session
|
// Generate a hash based on the current session
|
||||||
stateGenerator: (req) => {
|
static stateGenerator (req) {
|
||||||
const sessionCrypto = req.session.id + ':' + config.server.session_secret
|
const sessionCrypto = req.session.id + ':' + config.server.session_secret
|
||||||
return crypto.createHash('sha256').update(sessionCrypto).digest('hex')
|
return crypto.createHash('sha256').update(sessionCrypto).digest('hex')
|
||||||
},
|
}
|
||||||
|
|
||||||
// Find an user with an external ID
|
// Find an user with an external ID
|
||||||
getExternal: async (service, identifier) => {
|
static async getExternal (service, identifier) {
|
||||||
let extr = await models.External.query().where('service', service).andWhere('identifier', identifier)
|
let extr = await models.External.query().where('service', service).andWhere('identifier', identifier)
|
||||||
if (!extr || !extr.length) return null
|
if (!extr || !extr.length) return null
|
||||||
extr = extr[0]
|
extr = extr[0]
|
||||||
extr.user = null
|
extr.user = null
|
||||||
|
|
||||||
if (extr.user_id !== null) {
|
if (extr.user_id !== null) {
|
||||||
const user = await UAPI.User.get(extr.user_id)
|
const user = await User.get(extr.user_id)
|
||||||
if (user) {
|
if (user) {
|
||||||
extr.user = user
|
extr.user = user
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return extr
|
return extr
|
||||||
},
|
}
|
||||||
|
|
||||||
// Get user ban status
|
// Get user ban status
|
||||||
getBan: async (user, ipAddress) => {
|
static async getBan (user, ipAddress) {
|
||||||
const banList = await UAPI.User.getBanStatus(ipAddress || user.id, ipAddress != null)
|
const banList = await User.getBanStatus(ipAddress || user.id, ipAddress != null)
|
||||||
return banList
|
return banList
|
||||||
},
|
}
|
||||||
|
|
||||||
// Create a new `external` instance for a user
|
// Create a new `external` instance for a user
|
||||||
new: async (service, identifier, user) => {
|
static async new (service, identifier, user) {
|
||||||
const data = {
|
const data = {
|
||||||
user_id: user.id,
|
user_id: user.id,
|
||||||
service: service,
|
service: service,
|
||||||
@ -51,11 +53,12 @@ const API = {
|
|||||||
created_at: new Date()
|
created_at: new Date()
|
||||||
}
|
}
|
||||||
|
|
||||||
await await models.External.query().insert(data)
|
await models.External.query().insert(data)
|
||||||
return true
|
return true
|
||||||
},
|
}
|
||||||
|
|
||||||
// Create a new user
|
// Create a new user
|
||||||
newUser: async (service, identifier, data) => {
|
static async newUser (service, identifier, data) {
|
||||||
if (config.external.registrations !== true) throw new Error('Registrations from third-party websites are not allowed.')
|
if (config.external.registrations !== true) throw new Error('Registrations from third-party websites are not allowed.')
|
||||||
const udataLimited = Object.assign({
|
const udataLimited = Object.assign({
|
||||||
activated: 1,
|
activated: 1,
|
||||||
@ -76,14 +79,14 @@ const API = {
|
|||||||
udataLimited.username = udataLimited.username.substring(0, 26)
|
udataLimited.username = udataLimited.username.substring(0, 26)
|
||||||
|
|
||||||
// Check if the username is already taken
|
// Check if the username is already taken
|
||||||
if (await UAPI.User.get(udataLimited.username) != null || udataLimited.username.length < 4) {
|
if (await User.get(udataLimited.username) != null || udataLimited.username.length < 4) {
|
||||||
udataLimited.username = udataLimited.username + UAPI.Hash(4)
|
udataLimited.username = udataLimited.username + Hash(4)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the email given to us is already registered, if so,
|
// Check if the email given to us is already registered, if so,
|
||||||
// tell them to log in first.
|
// tell them to log in first.
|
||||||
if (udataLimited.email && udataLimited.email !== '') {
|
if (udataLimited.email && udataLimited.email !== '') {
|
||||||
const getByEmail = await UAPI.User.get(udataLimited.email)
|
const getByEmail = await User.get(udataLimited.email)
|
||||||
if (getByEmail) {
|
if (getByEmail) {
|
||||||
throw new Error('An user with this email address is already registered, but this external account is are not linked. If you wish to link the account, please log in first.')
|
throw new Error('An user with this email address is already registered, but this external account is are not linked. If you wish to link the account, please log in first.')
|
||||||
}
|
}
|
||||||
@ -91,13 +94,14 @@ const API = {
|
|||||||
|
|
||||||
// Create a new user based on the information we got from an external service
|
// Create a new user based on the information we got from an external service
|
||||||
const newUser = await models.User.query().insert(udataLimited)
|
const newUser = await models.User.query().insert(udataLimited)
|
||||||
await API.Common.new(service, identifier, newUser)
|
await Common.new(service, identifier, newUser)
|
||||||
|
|
||||||
return newUser
|
return newUser
|
||||||
},
|
}
|
||||||
|
|
||||||
// Remove an `external` object (thus unlinking from a service)
|
// Remove an `external` object (thus unlinking from a service)
|
||||||
remove: async (user, service) => {
|
static async remove (user, service) {
|
||||||
user = await UAPI.User.ensureObject(user, ['password'])
|
user = await User.ensureObject(user, ['password'])
|
||||||
const userExterns = await models.External.query().orderBy('created_at', 'asc').where('user_id', user.id)
|
const userExterns = await models.External.query().orderBy('created_at', 'asc').where('user_id', user.id)
|
||||||
|
|
||||||
if (!userExterns.length) {
|
if (!userExterns.length) {
|
||||||
@ -110,33 +114,34 @@ const API = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return models.External.query().delete().where('user_id', user.id).andWhere('service', service)
|
return models.External.query().delete().where('user_id', user.id).andWhere('service', service)
|
||||||
},
|
}
|
||||||
|
|
||||||
// Common code for all auth callbacks
|
// Common code for all auth callbacks
|
||||||
callback: async (identifier, uid, user, ipAddress, remoteData, avatarFunc) => {
|
static async callback (identifier, uid, user, ipAddress, remoteData, avatarFunc) {
|
||||||
const exists = await API.Common.getExternal(identifier, uid)
|
const exists = await Common.getExternal(identifier, uid)
|
||||||
|
|
||||||
if (user) {
|
if (user) {
|
||||||
// Get bans for user
|
// Get bans for user
|
||||||
const bans = await API.Common.getBan(user)
|
const bans = await Common.getBan(user)
|
||||||
if (bans.length) return { banned: bans, ip: false }
|
if (bans.length) return { banned: bans, ip: false }
|
||||||
|
|
||||||
if (exists) return { error: null, user: user }
|
if (exists) return { error: null, user: user }
|
||||||
|
|
||||||
await API.Common.new(identifier, uid, user)
|
await Common.new(identifier, uid, user)
|
||||||
return { error: null, user: user }
|
return { error: null, user: user }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Callback succeeded with user id and the external table exists, we log in the user
|
// Callback succeeded with user id and the external table exists, we log in the user
|
||||||
if (exists) {
|
if (exists) {
|
||||||
// Get bans for user
|
// Get bans for user
|
||||||
const bans = await API.Common.getBan(exists.user)
|
const bans = await Common.getBan(exists.user)
|
||||||
|
|
||||||
if (bans.length) return { banned: bans, ip: false }
|
if (bans.length) return { banned: bans, ip: false }
|
||||||
return { error: null, user: exists.user }
|
return { error: null, user: exists.user }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get bans for IP address
|
// Get bans for IP address
|
||||||
const bans = await API.Common.getBan(null, ipAddress)
|
const bans = await Common.getBan(null, ipAddress)
|
||||||
if (bans.length) return { banned: bans, ip: true }
|
if (bans.length) return { banned: bans, ip: true }
|
||||||
|
|
||||||
// Run the function for avatar fetching
|
// Run the function for avatar fetching
|
||||||
@ -161,16 +166,17 @@ const API = {
|
|||||||
|
|
||||||
let newUser
|
let newUser
|
||||||
try {
|
try {
|
||||||
newUser = await API.Common.newUser(identifier, uid, newUData)
|
newUser = await Common.newUser(identifier, uid, newUData)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return { error: e.message }
|
return { error: e.message }
|
||||||
}
|
}
|
||||||
|
|
||||||
return { error: null, user: newUser }
|
return { error: null, user: newUser }
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Facebook: {
|
|
||||||
getAvatar: async (rawData) => {
|
export class Facebook {
|
||||||
|
static async getAvatar (rawData) {
|
||||||
let profilepic = null
|
let profilepic = null
|
||||||
|
|
||||||
if (rawData.picture) {
|
if (rawData.picture) {
|
||||||
@ -183,8 +189,9 @@ const API = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return profilepic
|
return profilepic
|
||||||
},
|
}
|
||||||
callback: async (user, authResponse, ipAddress) => {
|
|
||||||
|
static async callback (user, authResponse, ipAddress) {
|
||||||
if (!authResponse) {
|
if (!authResponse) {
|
||||||
return { error: 'No Authorization' }
|
return { error: 'No Authorization' }
|
||||||
}
|
}
|
||||||
@ -202,7 +209,7 @@ const API = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
fbdata = await http.GET('https://graph.facebook.com/v2.10/' + uid + '?' + qs.stringify(intel))
|
fbdata = await httpGET('https://graph.facebook.com/v2.10/' + uid + '?' + qs.stringify(intel))
|
||||||
fbdata = JSON.parse(fbdata)
|
fbdata = JSON.parse(fbdata)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return { error: 'Could not get user information', errorObject: e }
|
return { error: 'Could not get user information', errorObject: e }
|
||||||
@ -213,16 +220,17 @@ const API = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const cleanedData = Object.assign(fbdata, {
|
const cleanedData = Object.assign(fbdata, {
|
||||||
username: fbdata.short_name || 'FB' + UAPI.Hash(4),
|
username: fbdata.short_name || 'FB' + Hash(4),
|
||||||
display_name: fbdata.name,
|
display_name: fbdata.name,
|
||||||
email: fbdata.email || ''
|
email: fbdata.email || ''
|
||||||
})
|
})
|
||||||
|
|
||||||
return API.Common.callback('facebook', uid, user, ipAddress, cleanedData, API.Facebook.getAvatar)
|
return Common.callback('facebook', uid, user, ipAddress, cleanedData, Facebook.getAvatar)
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Twitter: {
|
|
||||||
getAvatar: async function (rawData) {
|
export class Twitter {
|
||||||
|
static async getAvatar (rawData) {
|
||||||
let profilepic = null
|
let profilepic = null
|
||||||
|
|
||||||
if (rawData.profile_image_url_https) {
|
if (rawData.profile_image_url_https) {
|
||||||
@ -233,8 +241,9 @@ const API = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return profilepic
|
return profilepic
|
||||||
},
|
}
|
||||||
oauthApp: function () {
|
|
||||||
|
static oauthApp () {
|
||||||
if (!twitterApp) {
|
if (!twitterApp) {
|
||||||
const redirectUri = config.server.domain + '/api/external/twitter/callback'
|
const redirectUri = config.server.domain + '/api/external/twitter/callback'
|
||||||
twitterApp = new oauth.PromiseOAuth(
|
twitterApp = new oauth.PromiseOAuth(
|
||||||
@ -247,9 +256,10 @@ const API = {
|
|||||||
'HMAC-SHA1'
|
'HMAC-SHA1'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
getRequestToken: async function () {
|
|
||||||
if (!twitterApp) API.Twitter.oauthApp()
|
static async getRequestToken () {
|
||||||
|
if (!twitterApp) Twitter.oauthApp()
|
||||||
let tokens
|
let tokens
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -262,9 +272,10 @@ const API = {
|
|||||||
if (tokens[2].oauth_callback_confirmed !== 'true') return { error: 'No tokens returned.' }
|
if (tokens[2].oauth_callback_confirmed !== 'true') return { error: 'No tokens returned.' }
|
||||||
|
|
||||||
return { error: null, token: tokens[0], token_secret: tokens[1] }
|
return { error: null, token: tokens[0], token_secret: tokens[1] }
|
||||||
},
|
}
|
||||||
getAccessTokens: async function (token, secret, verifier) {
|
|
||||||
if (!twitterApp) API.Twitter.oauthApp()
|
static async getAccessTokens (token, secret, verifier) {
|
||||||
|
if (!twitterApp) Twitter.oauthApp()
|
||||||
let tokens
|
let tokens
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -277,9 +288,10 @@ const API = {
|
|||||||
if (!tokens || !tokens.length) return { error: 'No tokens returned' }
|
if (!tokens || !tokens.length) return { error: 'No tokens returned' }
|
||||||
|
|
||||||
return { error: null, access_token: tokens[0], access_token_secret: tokens[1] }
|
return { error: null, access_token: tokens[0], access_token_secret: tokens[1] }
|
||||||
},
|
}
|
||||||
callback: async function (user, accessTokens, ipAddress) {
|
|
||||||
if (!twitterApp) API.Twitter.oauthApp()
|
static async callback (user, accessTokens, ipAddress) {
|
||||||
|
if (!twitterApp) Twitter.oauthApp()
|
||||||
let twdata
|
let twdata
|
||||||
try {
|
try {
|
||||||
const resp = await twitterApp.get('https://api.twitter.com/1.1/account/verify_credentials.json?include_email=true',
|
const resp = await twitterApp.get('https://api.twitter.com/1.1/account/verify_credentials.json?include_email=true',
|
||||||
@ -298,11 +310,12 @@ const API = {
|
|||||||
email: twdata.email || ''
|
email: twdata.email || ''
|
||||||
})
|
})
|
||||||
|
|
||||||
return API.Common.callback('twitter', uid, user, ipAddress, cleanedData, API.Twitter.getAvatar)
|
return Common.callback('twitter', uid, user, ipAddress, cleanedData, Twitter.getAvatar)
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Google: {
|
|
||||||
getAvatar: async (rawData) => {
|
export class Google {
|
||||||
|
static async getAvatar (rawData) {
|
||||||
let profilepic = null
|
let profilepic = null
|
||||||
if (rawData.image) {
|
if (rawData.image) {
|
||||||
const imgdata = await Image.downloadImage(rawData.image)
|
const imgdata = await Image.downloadImage(rawData.image)
|
||||||
@ -312,12 +325,13 @@ const API = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return profilepic
|
return profilepic
|
||||||
},
|
}
|
||||||
callback: async (user, data, ipAddress) => {
|
|
||||||
|
static async callback (user, data, ipAddress) {
|
||||||
let uid
|
let uid
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const test = await http.GET('https://www.googleapis.com/oauth2/v3/tokeninfo?id_token=' + data.id_token)
|
const test = await httpGET('https://www.googleapis.com/oauth2/v3/tokeninfo?id_token=' + data.id_token)
|
||||||
if (!test) throw new Error('No response!')
|
if (!test) throw new Error('No response!')
|
||||||
|
|
||||||
const jsondata = JSON.parse(test)
|
const jsondata = JSON.parse(test)
|
||||||
@ -338,11 +352,12 @@ const API = {
|
|||||||
email: data.email || ''
|
email: data.email || ''
|
||||||
})
|
})
|
||||||
|
|
||||||
return API.Common.callback('google', uid, user, ipAddress, cleanedData, API.Google.getAvatar)
|
return Common.callback('google', uid, user, ipAddress, cleanedData, Google.getAvatar)
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Discord: {
|
|
||||||
getAvatar: async (rawData) => {
|
export class Discord {
|
||||||
|
static async getAvatar (rawData) {
|
||||||
let profilepic = null
|
let profilepic = null
|
||||||
const aviSnowflake = rawData.avatar
|
const aviSnowflake = rawData.avatar
|
||||||
if (aviSnowflake) {
|
if (aviSnowflake) {
|
||||||
@ -357,8 +372,9 @@ const API = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return profilepic
|
return profilepic
|
||||||
},
|
}
|
||||||
oauth2App: function () {
|
|
||||||
|
static oauth2App () {
|
||||||
if (discordApp) return
|
if (discordApp) return
|
||||||
discordApp = new oauth.PromiseOAuth2(
|
discordApp = new oauth.PromiseOAuth2(
|
||||||
config.external.discord.api,
|
config.external.discord.api,
|
||||||
@ -369,10 +385,11 @@ const API = {
|
|||||||
)
|
)
|
||||||
|
|
||||||
discordApp.useAuthorizationHeaderforGET(true)
|
discordApp.useAuthorizationHeaderforGET(true)
|
||||||
},
|
}
|
||||||
getAuthorizeURL: function (req) {
|
|
||||||
if (!discordApp) API.Discord.oauth2App()
|
static getAuthorizeURL (req) {
|
||||||
const state = API.Common.stateGenerator(req)
|
if (!discordApp) Discord.oauth2App()
|
||||||
|
const state = Common.stateGenerator(req)
|
||||||
const redirectUri = config.server.domain + '/api/external/discord/callback'
|
const redirectUri = config.server.domain + '/api/external/discord/callback'
|
||||||
|
|
||||||
const params = {
|
const params = {
|
||||||
@ -386,9 +403,10 @@ const API = {
|
|||||||
const url = discordApp.getAuthorizeUrl(params)
|
const url = discordApp.getAuthorizeUrl(params)
|
||||||
|
|
||||||
return { error: null, state: state, url: url }
|
return { error: null, state: state, url: url }
|
||||||
},
|
}
|
||||||
getAccessToken: async function (code) {
|
|
||||||
if (!discordApp) API.Discord.oauth2App()
|
static async getAccessToken (code) {
|
||||||
|
if (!discordApp) Discord.oauth2App()
|
||||||
|
|
||||||
const redirectUri = config.server.domain + '/api/external/discord/callback'
|
const redirectUri = config.server.domain + '/api/external/discord/callback'
|
||||||
let tokens
|
let tokens
|
||||||
@ -403,9 +421,10 @@ const API = {
|
|||||||
tokens = tokens[2]
|
tokens = tokens[2]
|
||||||
|
|
||||||
return { error: null, accessToken: tokens.access_token }
|
return { error: null, accessToken: tokens.access_token }
|
||||||
},
|
}
|
||||||
callback: async function (user, accessToken, ipAddress) {
|
|
||||||
if (!discordApp) API.Discord.oauth2App()
|
static async callback (user, accessToken, ipAddress) {
|
||||||
|
if (!discordApp) Discord.oauth2App()
|
||||||
|
|
||||||
let ddata
|
let ddata
|
||||||
try {
|
try {
|
||||||
@ -424,9 +443,6 @@ const API = {
|
|||||||
email: ddata.email || ''
|
email: ddata.email || ''
|
||||||
})
|
})
|
||||||
|
|
||||||
return API.Common.callback('discord', uid, user, ipAddress, cleanedData, API.Discord.getAvatar)
|
return Common.callback('discord', uid, user, ipAddress, cleanedData, Discord.getAvatar)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = API
|
|
||||||
|
@ -4,7 +4,7 @@ import path from 'path'
|
|||||||
import crypto from 'crypto'
|
import crypto from 'crypto'
|
||||||
import { v4 as uuid } from 'uuid'
|
import { v4 as uuid } from 'uuid'
|
||||||
|
|
||||||
import http from '../../scripts/http'
|
import { downloadURL } from '../../scripts/http'
|
||||||
|
|
||||||
const fs = require('fs-extra')
|
const fs = require('fs-extra')
|
||||||
|
|
||||||
@ -13,7 +13,7 @@ const gravatar = 'https://www.gravatar.com/avatar/'
|
|||||||
const uploads = path.join(__dirname, '../../', 'usercontent')
|
const uploads = path.join(__dirname, '../../', 'usercontent')
|
||||||
const images = path.join(uploads, 'images')
|
const images = path.join(uploads, 'images')
|
||||||
const maxFileSize = 1000000
|
const maxFileSize = 1000000
|
||||||
const imageTypes = {
|
export const imageTypes = {
|
||||||
'image/png': '.png',
|
'image/png': '.png',
|
||||||
'image/jpg': '.jpg',
|
'image/jpg': '.jpg',
|
||||||
'image/jpeg': '.jpeg'
|
'image/jpeg': '.jpeg'
|
||||||
@ -51,7 +51,7 @@ async function bailOut (file, error) {
|
|||||||
throw new Error(error)
|
throw new Error(error)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function imageBase64 (baseObj) {
|
export async function imageBase64 (baseObj) {
|
||||||
if (!baseObj) return null
|
if (!baseObj) return null
|
||||||
const imgData = decodeBase64Image(baseObj)
|
const imgData = decodeBase64Image(baseObj)
|
||||||
|
|
||||||
@ -75,23 +75,23 @@ async function imageBase64 (baseObj) {
|
|||||||
return { file: fpath }
|
return { file: fpath }
|
||||||
}
|
}
|
||||||
|
|
||||||
function gravatarURL (email) {
|
export function gravatarURL (email) {
|
||||||
const sum = crypto.createHash('md5').update(email).digest('hex')
|
const sum = crypto.createHash('md5').update(email).digest('hex')
|
||||||
return gravatar + sum + '.jpg'
|
return gravatar + sum + '.jpg'
|
||||||
}
|
}
|
||||||
|
|
||||||
async function downloadImage (imgUrl, designation) {
|
export async function downloadImage (imgUrl, designation) {
|
||||||
if (!imgUrl) return null
|
if (!imgUrl) return null
|
||||||
if (!designation) designation = 'download'
|
if (!designation) designation = 'download'
|
||||||
|
|
||||||
let imageName = designation + '-' + uuid()
|
let imageName = designation + '-' + uuid()
|
||||||
const uridata = new URL(imgUrl)
|
const uridata = new URL(imgUrl)
|
||||||
const pathdata = path.parse(uridata.path)
|
const pathdata = path.parse(uridata.href)
|
||||||
|
|
||||||
imageName += pathdata.ext || '.png'
|
imageName += pathdata.ext || '.png'
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await http.Download(imgUrl, path.join(images, imageName))
|
await downloadURL(imgUrl, path.join(images, imageName))
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@ -99,7 +99,7 @@ async function downloadImage (imgUrl, designation) {
|
|||||||
return imageName
|
return imageName
|
||||||
}
|
}
|
||||||
|
|
||||||
async function uploadImage (identifier, fields, files) {
|
export async function uploadImage (identifier, fields, files) {
|
||||||
if (!files.image) throw new Error('No image file')
|
if (!files.image) throw new Error('No image file')
|
||||||
|
|
||||||
let file = files.image[0]
|
let file = files.image[0]
|
||||||
@ -174,11 +174,3 @@ async function uploadImage (identifier, fields, files) {
|
|||||||
|
|
||||||
return { file: fileName }
|
return { file: fileName }
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
downloadImage: downloadImage,
|
|
||||||
uploadImage: uploadImage,
|
|
||||||
imageBase64: imageBase64,
|
|
||||||
gravatarURL: gravatarURL,
|
|
||||||
types: imageTypes
|
|
||||||
}
|
|
||||||
|
@ -7,9 +7,9 @@ import { v1 as uuidV1 } from 'uuid'
|
|||||||
import fs from 'fs-extra'
|
import fs from 'fs-extra'
|
||||||
|
|
||||||
import config from '../../scripts/load-config'
|
import config from '../../scripts/load-config'
|
||||||
import http from '../../scripts/http'
|
import { httpPOST } from '../../scripts/http'
|
||||||
import models from './models'
|
import * as models from './models'
|
||||||
import emailer from './emailer'
|
import { pushMail } from './emailer'
|
||||||
|
|
||||||
const emailRe = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
|
const emailRe = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
|
||||||
|
|
||||||
@ -62,7 +62,7 @@ async function cleanUpDonation (obj, mcOnly, timeframe) {
|
|||||||
let user
|
let user
|
||||||
|
|
||||||
if (obj.user_id) {
|
if (obj.user_id) {
|
||||||
user = await API.User.get(obj.user_id)
|
user = await User.get(obj.user_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = {
|
const result = {
|
||||||
@ -92,12 +92,12 @@ async function cleanUpDonation (obj, mcOnly, timeframe) {
|
|||||||
|
|
||||||
const txnStore = []
|
const txnStore = []
|
||||||
|
|
||||||
const API = {
|
export function Hash (len) {
|
||||||
Hash: (len) => {
|
|
||||||
return crypto.randomBytes(len).toString('hex')
|
return crypto.randomBytes(len).toString('hex')
|
||||||
},
|
}
|
||||||
|
|
||||||
/* ppp - Posts Per Page; dcount - Post Count; page - number of current page */
|
/* ppp - Posts Per Page; dcount - Post Count; page - number of current page */
|
||||||
Pagination: (ppp, dcount, page) => {
|
export function Pagination (ppp, dcount, page) {
|
||||||
if (!ppp) ppp = 5
|
if (!ppp) ppp = 5
|
||||||
if (!dcount) return null
|
if (!dcount) return null
|
||||||
|
|
||||||
@ -113,143 +113,16 @@ const API = {
|
|||||||
offset: offset,
|
offset: offset,
|
||||||
total: dcount
|
total: dcount
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
User: {
|
|
||||||
get: async function (identifier) {
|
|
||||||
let scope = 'id'
|
|
||||||
if (typeof identifier === 'string') {
|
|
||||||
scope = 'username'
|
|
||||||
if (identifier.indexOf('@') !== -1) {
|
|
||||||
scope = 'email'
|
|
||||||
} else if (identifier.length === 36 && identifier.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i)) {
|
|
||||||
scope = 'uuid'
|
|
||||||
}
|
|
||||||
} else if (typeof identifier === 'object') {
|
|
||||||
if (identifier.id != null) {
|
|
||||||
identifier = identifier.id
|
|
||||||
} else if (identifier.username) {
|
|
||||||
scope = 'username'
|
|
||||||
identifier = identifier.username
|
|
||||||
} else {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const user = await models.User.query().where(scope, identifier)
|
export class Login {
|
||||||
if (!user.length) return null
|
static async password (user, password) {
|
||||||
|
user = await User.ensureObject(user, ['password'])
|
||||||
return user[0]
|
|
||||||
},
|
|
||||||
ensureObject: async function (user, fieldsPresent = ['id']) {
|
|
||||||
if (typeof user !== 'object' || !keysAvailable(user, fieldsPresent)) {
|
|
||||||
return API.User.get(user)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (user.id) {
|
|
||||||
return user
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
},
|
|
||||||
socialStatus: async function (user) {
|
|
||||||
user = await API.User.ensureObject(user, ['password'])
|
|
||||||
if (!user) return null
|
|
||||||
const external = await models.External.query().orderBy('created_at', 'asc').where('user_id', user.id)
|
|
||||||
const enabled = {}
|
|
||||||
|
|
||||||
for (const i in external) {
|
|
||||||
const ext = external[i]
|
|
||||||
enabled[ext.service] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
const accountSourceIsExternal = user.password === null || user.password === ''
|
|
||||||
const 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) throw new Error('No such user.')
|
|
||||||
|
|
||||||
data = Object.assign({
|
|
||||||
updated_at: new Date()
|
|
||||||
}, data)
|
|
||||||
|
|
||||||
return models.User.query().patchAndFetchById(user.id, data)
|
|
||||||
},
|
|
||||||
changeAvatar: async function (user, fileName) {
|
|
||||||
user = await API.User.ensureObject(user, ['avatar_file'])
|
|
||||||
const uploadsDir = path.join(__dirname, '../../', 'usercontent', 'images')
|
|
||||||
const pathOf = path.join(uploadsDir, fileName)
|
|
||||||
|
|
||||||
if (!await fs.exists(pathOf)) {
|
|
||||||
throw new Error('No such file')
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete previous upload
|
|
||||||
if (user.avatar_file != null) {
|
|
||||||
const file = path.join(uploadsDir, user.avatar_file)
|
|
||||||
if (await fs.exists(file)) {
|
|
||||||
await fs.unlink(file)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await API.User.update(user, { avatar_file: fileName })
|
|
||||||
return fileName
|
|
||||||
},
|
|
||||||
removeAvatar: async function (user) {
|
|
||||||
user = await API.User.ensureObject(user, ['avatar_file'])
|
|
||||||
const uploadsDir = path.join(__dirname, '../../', 'usercontent', 'images')
|
|
||||||
if (!user.avatar_file) return {}
|
|
||||||
|
|
||||||
const file = path.join(uploadsDir, user.avatar_file)
|
|
||||||
if (await fs.exists(file)) {
|
|
||||||
await fs.unlink(file)
|
|
||||||
}
|
|
||||||
|
|
||||||
return API.User.update(user, { avatar_file: null })
|
|
||||||
},
|
|
||||||
getBanStatus: async function (field, ip = false) {
|
|
||||||
let bans
|
|
||||||
if (ip === true) {
|
|
||||||
bans = await models.Ban.query().where('associated_ip', field)
|
|
||||||
} else {
|
|
||||||
bans = await models.Ban.query().where('user_id', field)
|
|
||||||
}
|
|
||||||
|
|
||||||
const bansActive = []
|
|
||||||
|
|
||||||
for (const i in bans) {
|
|
||||||
const ban = bans[i]
|
|
||||||
|
|
||||||
// Check expiry
|
|
||||||
if (ban.expires_at && new Date(ban.expires_at).getTime() < Date.now()) continue
|
|
||||||
|
|
||||||
const banInfo = {
|
|
||||||
banned: ban.created_at,
|
|
||||||
reason: ban.reason,
|
|
||||||
expiry: ban.expires_at
|
|
||||||
}
|
|
||||||
|
|
||||||
bansActive.push(banInfo)
|
|
||||||
}
|
|
||||||
|
|
||||||
return bansActive
|
|
||||||
},
|
|
||||||
Login: {
|
|
||||||
password: async function (user, password) {
|
|
||||||
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 })
|
||||||
},
|
}
|
||||||
activationToken: async function (token) {
|
|
||||||
|
static async activationToken (token) {
|
||||||
let getToken = await models.Token.query().where('token', token).andWhere('type', 1)
|
let getToken = await models.Token.query().where('token', token).andWhere('type', 1)
|
||||||
if (!getToken || !getToken.length) return false
|
if (!getToken || !getToken.length) return false
|
||||||
|
|
||||||
@ -257,23 +130,25 @@ const API = {
|
|||||||
|
|
||||||
if (getToken.expires_at && new Date(getToken.expires_at).getTime() < Date.now()) return false
|
if (getToken.expires_at && new Date(getToken.expires_at).getTime() < Date.now()) return false
|
||||||
|
|
||||||
const user = await API.User.get(getToken.user_id)
|
const user = await User.get(getToken.user_id)
|
||||||
if (!user) return false
|
if (!user) return false
|
||||||
|
|
||||||
await models.User.query().patchAndFetchById(user.id, { activated: 1 })
|
await models.User.query().patchAndFetchById(user.id, { activated: 1 })
|
||||||
await models.Token.query().delete().where('id', getToken.id)
|
await models.Token.query().delete().where('id', getToken.id)
|
||||||
return true
|
return true
|
||||||
},
|
}
|
||||||
totpTokenRequired: async function (user) {
|
|
||||||
|
static async totpTokenRequired (user) {
|
||||||
const getToken = await models.TotpToken.query().where('user_id', user.id)
|
const getToken = await models.TotpToken.query().where('user_id', user.id)
|
||||||
|
|
||||||
if (!getToken || !getToken.length) return false
|
if (!getToken || !getToken.length) return false
|
||||||
if (getToken[0].activated !== 1) return false
|
if (getToken[0].activated !== 1) return false
|
||||||
|
|
||||||
return true
|
return true
|
||||||
},
|
}
|
||||||
totpCheck: async function (user, code, emerg) {
|
|
||||||
user = await API.User.ensureObject(user)
|
static async totpCheck (user, code, emerg) {
|
||||||
|
user = await User.ensureObject(user)
|
||||||
let getToken = await models.TotpToken.query().where('user_id', user.id)
|
let getToken = await models.TotpToken.query().where('user_id', user.id)
|
||||||
if (!getToken || !getToken.length) return false
|
if (!getToken || !getToken.length) return false
|
||||||
getToken = getToken[0]
|
getToken = getToken[0]
|
||||||
@ -302,19 +177,21 @@ const API = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
},
|
}
|
||||||
purgeTotp: async function (user, password) {
|
|
||||||
user = await API.User.ensureObject(user, ['password'])
|
static async purgeTotp (user, password) {
|
||||||
const pwmatch = await API.User.Login.password(user, password)
|
user = await User.ensureObject(user, ['password'])
|
||||||
|
const pwmatch = await User.Login.password(user, password)
|
||||||
if (!pwmatch) return false
|
if (!pwmatch) return false
|
||||||
|
|
||||||
// TODO: Inform user via email
|
// TODO: Inform user via email
|
||||||
await models.TotpToken.query().delete().where('user_id', user.id)
|
await models.TotpToken.query().delete().where('user_id', user.id)
|
||||||
|
|
||||||
return true
|
return true
|
||||||
},
|
}
|
||||||
totpAquire: async function (user) {
|
|
||||||
user = await API.User.ensureObject(user, ['password'])
|
static async totpAquire (user) {
|
||||||
|
user = await User.ensureObject(user, ['password'])
|
||||||
|
|
||||||
// Do not allow totp for users who have registered using an external service
|
// Do not allow totp for users who have registered using an external service
|
||||||
if (!user.password || user.password === '') return null
|
if (!user.password || user.password === '') return null
|
||||||
@ -327,8 +204,8 @@ const API = {
|
|||||||
|
|
||||||
const newToken = {
|
const newToken = {
|
||||||
user_id: user.id,
|
user_id: user.id,
|
||||||
token: API.Hash(16),
|
token: Hash(16),
|
||||||
recovery_code: API.Hash(8),
|
recovery_code: Hash(8),
|
||||||
activated: 0,
|
activated: 0,
|
||||||
created_at: new Date()
|
created_at: new Date()
|
||||||
}
|
}
|
||||||
@ -342,144 +219,11 @@ const API = {
|
|||||||
|
|
||||||
return uri
|
return uri
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Register: {
|
|
||||||
hashPassword: async function (password) {
|
|
||||||
return bcryptTask({ task: 'hash', password: password })
|
|
||||||
},
|
|
||||||
validateEmail: (email) => {
|
|
||||||
return emailRe.test(email)
|
|
||||||
},
|
|
||||||
newAccount: async function (regdata) {
|
|
||||||
const email = config.email && config.email.enabled
|
|
||||||
const data = Object.assign(regdata, {
|
|
||||||
created_at: new Date(),
|
|
||||||
updated_at: new Date(),
|
|
||||||
uuid: uuidV1(),
|
|
||||||
activated: email ? 0 : 1
|
|
||||||
})
|
|
||||||
|
|
||||||
const userTest = await API.User.get(regdata.username)
|
export class OAuth2 {
|
||||||
if (userTest) {
|
static async getUserAuthorizations (user) {
|
||||||
throw new Error('This username is already taken!')
|
user = await User.ensureObject(user)
|
||||||
}
|
|
||||||
|
|
||||||
const emailTest = await API.User.get(regdata.email)
|
|
||||||
if (emailTest) {
|
|
||||||
throw new Error('This email address is already registered!')
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create user
|
|
||||||
const user = await models.User.query().insert(data)
|
|
||||||
|
|
||||||
if (email) {
|
|
||||||
await API.User.Register.activationEmail(user, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
return user
|
|
||||||
},
|
|
||||||
activationEmail: async function (user, deleteOnFail = false) {
|
|
||||||
// Activation token
|
|
||||||
const activationToken = API.Hash(16)
|
|
||||||
|
|
||||||
await models.Token.query().insert({
|
|
||||||
expires_at: new Date(Date.now() + 86400000), // 1 day
|
|
||||||
token: activationToken,
|
|
||||||
user_id: user.id,
|
|
||||||
type: 1
|
|
||||||
})
|
|
||||||
|
|
||||||
console.debug('Activation token:', activationToken)
|
|
||||||
|
|
||||||
// Send Activation Email
|
|
||||||
try {
|
|
||||||
const em = await emailer.pushMail('activate', user.email, {
|
|
||||||
domain: config.server.domain,
|
|
||||||
display_name: user.display_name,
|
|
||||||
activation_token: activationToken
|
|
||||||
})
|
|
||||||
|
|
||||||
console.debug(em)
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e)
|
|
||||||
|
|
||||||
if (deleteOnFail) {
|
|
||||||
await models.User.query().delete().where('id', user.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error('Invalid email address!')
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Reset: {
|
|
||||||
reset: async function (email, passRequired = true) {
|
|
||||||
const emailEnabled = config.email && config.email.enabled
|
|
||||||
|
|
||||||
if (!emailEnabled) throw new Error('Cannot reset password.')
|
|
||||||
|
|
||||||
const user = await API.User.get(email)
|
|
||||||
if (!user) throw new Error('This email address does not match any user in our database.')
|
|
||||||
if (!user.password && passRequired) throw new Error('The user associated with this email address has used an external website to log in, thus the password cannot be reset.')
|
|
||||||
|
|
||||||
const recentTokens = await models.Token.query().where('user_id', user.id).andWhere('expires_at', '>', new Date()).andWhere('type', 2)
|
|
||||||
if (recentTokens.length >= 2) {
|
|
||||||
throw new Error('You\'ve made too many reset requests recently. Please slow down.')
|
|
||||||
}
|
|
||||||
|
|
||||||
const resetToken = API.Hash(16)
|
|
||||||
await models.Token.query().insert({
|
|
||||||
expires_at: new Date(Date.now() + 86400000), // 1 day
|
|
||||||
token: resetToken,
|
|
||||||
user_id: user.id,
|
|
||||||
type: 2
|
|
||||||
})
|
|
||||||
|
|
||||||
// Send Reset Email
|
|
||||||
console.debug('Reset token:', resetToken)
|
|
||||||
if (email) {
|
|
||||||
try {
|
|
||||||
const em = await emailer.pushMail('reset_password', user.email, {
|
|
||||||
domain: config.server.domain,
|
|
||||||
display_name: user.display_name,
|
|
||||||
reset_token: resetToken
|
|
||||||
})
|
|
||||||
|
|
||||||
console.debug(em)
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e)
|
|
||||||
throw new Error('Invalid email address!')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return resetToken
|
|
||||||
},
|
|
||||||
resetToken: async function (token) {
|
|
||||||
let getToken = await models.Token.query().where('token', token).andWhere('type', 2)
|
|
||||||
if (!getToken || !getToken.length) return null
|
|
||||||
|
|
||||||
getToken = getToken[0]
|
|
||||||
|
|
||||||
if (getToken.expires_at && new Date(getToken.expires_at).getTime() < Date.now()) return null
|
|
||||||
|
|
||||||
const user = await API.User.get(getToken.user_id)
|
|
||||||
if (!user) return null
|
|
||||||
|
|
||||||
return user
|
|
||||||
},
|
|
||||||
changePassword: async function (user, password, token) {
|
|
||||||
const hashed = await API.User.Register.hashPassword(password)
|
|
||||||
|
|
||||||
await models.User.query().patchAndFetchById(user.id, { password: hashed, updated_at: new Date() })
|
|
||||||
await models.Token.query().delete().where('token', token)
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
OAuth2: {
|
|
||||||
getUserAuthorizations: async function (user) {
|
|
||||||
user = await API.User.ensureObject(user)
|
|
||||||
const auths = await models.OAuth2AuthorizedClient.query().where('user_id', user.id)
|
const auths = await models.OAuth2AuthorizedClient.query().where('user_id', user.id)
|
||||||
|
|
||||||
const nicelist = []
|
const nicelist = []
|
||||||
@ -505,9 +249,10 @@ const API = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return nicelist
|
return nicelist
|
||||||
},
|
}
|
||||||
removeUserAuthorization: async function (user, clientId) {
|
|
||||||
user = await API.User.ensureObject(user)
|
static async removeUserAuthorization (user, clientId) {
|
||||||
|
user = await User.ensureObject(user)
|
||||||
const auth = await models.OAuth2AuthorizedClient.query().where('user_id', user.id).andWhere('client_id', clientId)
|
const auth = await models.OAuth2AuthorizedClient.query().where('user_id', user.id).andWhere('client_id', clientId)
|
||||||
if (!auth.length) return false
|
if (!auth.length) return false
|
||||||
|
|
||||||
@ -520,15 +265,293 @@ const API = {
|
|||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Register {
|
||||||
|
static async hashPassword (password) {
|
||||||
|
return bcryptTask({ task: 'hash', password: password })
|
||||||
}
|
}
|
||||||
},
|
|
||||||
Payment: {
|
static validateEmail (email) {
|
||||||
handleIPN: async function (body) {
|
return emailRe.test(email)
|
||||||
|
}
|
||||||
|
|
||||||
|
static async newAccount (regdata) {
|
||||||
|
const email = config.email && config.email.enabled
|
||||||
|
const data = Object.assign(regdata, {
|
||||||
|
created_at: new Date(),
|
||||||
|
updated_at: new Date(),
|
||||||
|
uuid: uuidV1(),
|
||||||
|
activated: email ? 0 : 1
|
||||||
|
})
|
||||||
|
|
||||||
|
const userTest = await User.get(regdata.username)
|
||||||
|
if (userTest) {
|
||||||
|
throw new Error('This username is already taken!')
|
||||||
|
}
|
||||||
|
|
||||||
|
const emailTest = await User.get(regdata.email)
|
||||||
|
if (emailTest) {
|
||||||
|
throw new Error('This email address is already registered!')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create user
|
||||||
|
const user = await models.User.query().insert(data)
|
||||||
|
|
||||||
|
if (email) {
|
||||||
|
await User.Register.activationEmail(user, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
return user
|
||||||
|
}
|
||||||
|
|
||||||
|
static async activationEmail (user, deleteOnFail = false) {
|
||||||
|
// Activation token
|
||||||
|
const activationToken = Hash(16)
|
||||||
|
|
||||||
|
await models.Token.query().insert({
|
||||||
|
expires_at: new Date(Date.now() + 86400000), // 1 day
|
||||||
|
token: activationToken,
|
||||||
|
user_id: user.id,
|
||||||
|
type: 1
|
||||||
|
})
|
||||||
|
|
||||||
|
console.debug('Activation token:', activationToken)
|
||||||
|
|
||||||
|
// Send Activation Email
|
||||||
|
try {
|
||||||
|
const em = await pushMail('activate', user.email, {
|
||||||
|
domain: config.server.domain,
|
||||||
|
display_name: user.display_name,
|
||||||
|
activation_token: activationToken
|
||||||
|
})
|
||||||
|
|
||||||
|
console.debug(em)
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
|
||||||
|
if (deleteOnFail) {
|
||||||
|
await models.User.query().delete().where('id', user.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error('Invalid email address!')
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Reset {
|
||||||
|
static async reset (email, passRequired = true) {
|
||||||
|
const emailEnabled = config.email && config.email.enabled
|
||||||
|
|
||||||
|
if (!emailEnabled) throw new Error('Cannot reset password.')
|
||||||
|
|
||||||
|
const user = await User.get(email)
|
||||||
|
if (!user) throw new Error('This email address does not match any user in our database.')
|
||||||
|
if (!user.password && passRequired) throw new Error('The user associated with this email address has used an external website to log in, thus the password cannot be reset.')
|
||||||
|
|
||||||
|
const recentTokens = await models.Token.query().where('user_id', user.id).andWhere('expires_at', '>', new Date()).andWhere('type', 2)
|
||||||
|
if (recentTokens.length >= 2) {
|
||||||
|
throw new Error('You\'ve made too many reset requests recently. Please slow down.')
|
||||||
|
}
|
||||||
|
|
||||||
|
const resetToken = Hash(16)
|
||||||
|
await models.Token.query().insert({
|
||||||
|
expires_at: new Date(Date.now() + 86400000), // 1 day
|
||||||
|
token: resetToken,
|
||||||
|
user_id: user.id,
|
||||||
|
type: 2
|
||||||
|
})
|
||||||
|
|
||||||
|
// Send Reset Email
|
||||||
|
console.debug('Reset token:', resetToken)
|
||||||
|
if (email) {
|
||||||
|
try {
|
||||||
|
const em = await pushMail('reset_password', user.email, {
|
||||||
|
domain: config.server.domain,
|
||||||
|
display_name: user.display_name,
|
||||||
|
reset_token: resetToken
|
||||||
|
})
|
||||||
|
|
||||||
|
console.debug(em)
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
throw new Error('Invalid email address!')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resetToken
|
||||||
|
}
|
||||||
|
|
||||||
|
static async resetToken (token) {
|
||||||
|
let getToken = await models.Token.query().where('token', token).andWhere('type', 2)
|
||||||
|
if (!getToken || !getToken.length) return null
|
||||||
|
|
||||||
|
getToken = getToken[0]
|
||||||
|
|
||||||
|
if (getToken.expires_at && new Date(getToken.expires_at).getTime() < Date.now()) return null
|
||||||
|
|
||||||
|
const user = await User.get(getToken.user_id)
|
||||||
|
if (!user) return null
|
||||||
|
|
||||||
|
return user
|
||||||
|
}
|
||||||
|
|
||||||
|
static async changePassword (user, password, token) {
|
||||||
|
const hashed = await User.Register.hashPassword(password)
|
||||||
|
|
||||||
|
await models.User.query().patchAndFetchById(user.id, { password: hashed, updated_at: new Date() })
|
||||||
|
await models.Token.query().delete().where('token', token)
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class User {
|
||||||
|
static async get (identifier) {
|
||||||
|
let scope = 'id'
|
||||||
|
if (typeof identifier === 'string') {
|
||||||
|
scope = 'username'
|
||||||
|
if (identifier.indexOf('@') !== -1) {
|
||||||
|
scope = 'email'
|
||||||
|
} else if (identifier.length === 36 && identifier.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i)) {
|
||||||
|
scope = 'uuid'
|
||||||
|
}
|
||||||
|
} else if (typeof identifier === 'object') {
|
||||||
|
if (identifier.id != null) {
|
||||||
|
identifier = identifier.id
|
||||||
|
} else if (identifier.username) {
|
||||||
|
scope = 'username'
|
||||||
|
identifier = identifier.username
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = await models.User.query().where(scope, identifier)
|
||||||
|
if (!user.length) return null
|
||||||
|
|
||||||
|
return user[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
static async ensureObject (user, fieldsPresent = ['id']) {
|
||||||
|
if (typeof user !== 'object' || !keysAvailable(user, fieldsPresent)) {
|
||||||
|
return User.get(user)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (user.id) {
|
||||||
|
return user
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
static async socialStatus (user) {
|
||||||
|
user = await User.ensureObject(user, ['password'])
|
||||||
|
if (!user) return null
|
||||||
|
const external = await models.External.query().orderBy('created_at', 'asc').where('user_id', user.id)
|
||||||
|
const enabled = {}
|
||||||
|
|
||||||
|
for (const i in external) {
|
||||||
|
const ext = external[i]
|
||||||
|
enabled[ext.service] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const accountSourceIsExternal = user.password === null || user.password === ''
|
||||||
|
const obj = {
|
||||||
|
enabled: enabled,
|
||||||
|
password: !accountSourceIsExternal
|
||||||
|
}
|
||||||
|
|
||||||
|
if (accountSourceIsExternal) {
|
||||||
|
obj.source = external[0].service
|
||||||
|
}
|
||||||
|
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
|
||||||
|
static async update (user, data) {
|
||||||
|
user = await User.ensureObject(user)
|
||||||
|
if (!user) throw new Error('No such user.')
|
||||||
|
|
||||||
|
data = Object.assign({
|
||||||
|
updated_at: new Date()
|
||||||
|
}, data)
|
||||||
|
|
||||||
|
return models.User.query().patchAndFetchById(user.id, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
static async changeAvatar (user, fileName) {
|
||||||
|
user = await User.ensureObject(user, ['avatar_file'])
|
||||||
|
const uploadsDir = path.join(__dirname, '../../', 'usercontent', 'images')
|
||||||
|
const pathOf = path.join(uploadsDir, fileName)
|
||||||
|
|
||||||
|
if (!await fs.pathExists(pathOf)) {
|
||||||
|
throw new Error('No such file')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete previous upload
|
||||||
|
if (user.avatar_file != null) {
|
||||||
|
const file = path.join(uploadsDir, user.avatar_file)
|
||||||
|
if (await fs.pathExists(file)) {
|
||||||
|
await fs.unlink(file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await User.update(user, { avatar_file: fileName })
|
||||||
|
return fileName
|
||||||
|
}
|
||||||
|
|
||||||
|
static async removeAvatar (user) {
|
||||||
|
user = await User.ensureObject(user, ['avatar_file'])
|
||||||
|
const uploadsDir = path.join(__dirname, '../../', 'usercontent', 'images')
|
||||||
|
if (!user.avatar_file) return {}
|
||||||
|
|
||||||
|
const file = path.join(uploadsDir, user.avatar_file)
|
||||||
|
if (await fs.pathExists(file)) {
|
||||||
|
await fs.unlink(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
return User.update(user, { avatar_file: null })
|
||||||
|
}
|
||||||
|
|
||||||
|
static async getBanStatus (field, ip = false) {
|
||||||
|
let bans
|
||||||
|
if (ip === true) {
|
||||||
|
bans = await models.Ban.query().where('associated_ip', field)
|
||||||
|
} else {
|
||||||
|
bans = await models.Ban.query().where('user_id', field)
|
||||||
|
}
|
||||||
|
|
||||||
|
const bansActive = []
|
||||||
|
|
||||||
|
for (const i in bans) {
|
||||||
|
const ban = bans[i]
|
||||||
|
|
||||||
|
// Check expiry
|
||||||
|
if (ban.expires_at && new Date(ban.expires_at).getTime() < Date.now()) continue
|
||||||
|
|
||||||
|
const banInfo = {
|
||||||
|
banned: ban.created_at,
|
||||||
|
reason: ban.reason,
|
||||||
|
expiry: ban.expires_at
|
||||||
|
}
|
||||||
|
|
||||||
|
bansActive.push(banInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
return bansActive
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Paymen {
|
||||||
|
static async handleIPN (body) {
|
||||||
const sandboxed = body.test_ipn === '1'
|
const sandboxed = body.test_ipn === '1'
|
||||||
const url = 'https://ipnpb.' + (sandboxed ? 'sandbox.' : '') + 'paypal.com/cgi-bin/webscr'
|
const url = 'https://ipnpb.' + (sandboxed ? 'sandbox.' : '') + 'paypal.com/cgi-bin/webscr'
|
||||||
|
|
||||||
console.debug('Incoming payment')
|
console.debug('Incoming payment')
|
||||||
const verification = await http.POST(url, {}, Object.assign({
|
const verification = await httpPOST(url, {}, Object.assign({
|
||||||
cmd: '_notify-validate'
|
cmd: '_notify-validate'
|
||||||
}, body))
|
}, body))
|
||||||
|
|
||||||
@ -568,9 +591,9 @@ const API = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (body.user_id != null) {
|
if (body.user_id != null) {
|
||||||
user = await API.User.get(body.user_id)
|
user = await User.get(body.user_id)
|
||||||
} else if (body.payer_email != null) {
|
} else if (body.payer_email != null) {
|
||||||
user = await API.User.get(body.payer_email)
|
user = await User.get(body.payer_email)
|
||||||
}
|
}
|
||||||
|
|
||||||
const donation = {
|
const donation = {
|
||||||
@ -585,9 +608,10 @@ const API = {
|
|||||||
console.log('Server receieved a successful PayPal IPN message.')
|
console.log('Server receieved a successful PayPal IPN message.')
|
||||||
|
|
||||||
return models.Donation.query().insert(donation)
|
return models.Donation.query().insert(donation)
|
||||||
},
|
}
|
||||||
userContributions: async function (user) {
|
|
||||||
user = await API.User.ensureObject(user)
|
static async userContributions (user) {
|
||||||
|
user = await User.ensureObject(user)
|
||||||
|
|
||||||
const dbq = await models.Donation.query().orderBy('created_at', 'desc').where('user_id', user.id)
|
const dbq = await models.Donation.query().orderBy('created_at', 'desc').where('user_id', user.id)
|
||||||
const contribs = []
|
const contribs = []
|
||||||
@ -597,8 +621,9 @@ const API = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return contribs
|
return contribs
|
||||||
},
|
}
|
||||||
allContributions: async function (count, mcOnly, timeframe = 0) {
|
|
||||||
|
static async allContributions (count, mcOnly, timeframe = 0) {
|
||||||
const dbq = await models.Donation.query().orderBy('created_at', 'desc').limit(count)
|
const dbq = await models.Donation.query().orderBy('created_at', 'desc').limit(count)
|
||||||
const contribs = []
|
const contribs = []
|
||||||
|
|
||||||
@ -610,7 +635,4 @@ const API = {
|
|||||||
|
|
||||||
return contribs
|
return contribs
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = API
|
|
||||||
|
@ -1,109 +1,91 @@
|
|||||||
import { Model } from '../../scripts/load-database'
|
import { Model } from '../../scripts/load-database'
|
||||||
|
|
||||||
class User extends Model {
|
export class User extends Model {
|
||||||
static get tableName () {
|
static get tableName () {
|
||||||
return 'users'
|
return 'users'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class External extends Model {
|
export class External extends Model {
|
||||||
static get tableName () {
|
static get tableName () {
|
||||||
return 'external'
|
return 'external'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Token extends Model {
|
export class Token extends Model {
|
||||||
static get tableName () {
|
static get tableName () {
|
||||||
return 'simple_token'
|
return 'simple_token'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class OAuth2Client extends Model {
|
export class OAuth2Client extends Model {
|
||||||
static get tableName () {
|
static get tableName () {
|
||||||
return 'oauth2_client'
|
return 'oauth2_client'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class OAuth2AuthorizedClient extends Model {
|
export class OAuth2AuthorizedClient extends Model {
|
||||||
static get tableName () {
|
static get tableName () {
|
||||||
return 'oauth2_client_authorization'
|
return 'oauth2_client_authorization'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class OAuth2Code extends Model {
|
export class OAuth2Code extends Model {
|
||||||
static get tableName () {
|
static get tableName () {
|
||||||
return 'oauth2_code'
|
return 'oauth2_code'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class OAuth2AccessToken extends Model {
|
export class OAuth2AccessToken extends Model {
|
||||||
static get tableName () {
|
static get tableName () {
|
||||||
return 'oauth2_access_token'
|
return 'oauth2_access_token'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class OAuth2RefreshToken extends Model {
|
export class OAuth2RefreshToken extends Model {
|
||||||
static get tableName () {
|
static get tableName () {
|
||||||
return 'oauth2_refresh_token'
|
return 'oauth2_refresh_token'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class TotpToken extends Model {
|
export class TotpToken extends Model {
|
||||||
static get tableName () {
|
static get tableName () {
|
||||||
return 'totp_token'
|
return 'totp_token'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Ban extends Model {
|
export class Ban extends Model {
|
||||||
static get tableName () {
|
static get tableName () {
|
||||||
return 'network_ban'
|
return 'network_ban'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class News extends Model {
|
export class News extends Model {
|
||||||
static get tableName () {
|
static get tableName () {
|
||||||
return 'news'
|
return 'news'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Donation extends Model {
|
export class Donation extends Model {
|
||||||
static get tableName () {
|
static get tableName () {
|
||||||
return 'donation'
|
return 'donation'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Subscription extends Model {
|
export class Subscription extends Model {
|
||||||
static get tableName () {
|
static get tableName () {
|
||||||
return 'subscription'
|
return 'subscription'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class MinecraftMember extends Model {
|
export class MinecraftMember extends Model {
|
||||||
static get tableName () {
|
static get tableName () {
|
||||||
return 'mc_member'
|
return 'mc_member'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class MinecraftToken extends Model {
|
export class MinecraftToken extends Model {
|
||||||
static get tableName () {
|
static get tableName () {
|
||||||
return 'mc_verify'
|
return 'mc_verify'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
User: User,
|
|
||||||
External: External,
|
|
||||||
Token: Token,
|
|
||||||
OAuth2Client: OAuth2Client,
|
|
||||||
OAuth2AuthorizedClient: OAuth2AuthorizedClient,
|
|
||||||
OAuth2Code: OAuth2Code,
|
|
||||||
OAuth2AccessToken: OAuth2AccessToken,
|
|
||||||
OAuth2RefreshToken: OAuth2RefreshToken,
|
|
||||||
TotpToken: TotpToken,
|
|
||||||
Ban: Ban,
|
|
||||||
News: News,
|
|
||||||
Donation: Donation,
|
|
||||||
Subscription: Subscription,
|
|
||||||
MinecraftMember: MinecraftMember,
|
|
||||||
MinecraftToken: MinecraftToken
|
|
||||||
}
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import API from './index'
|
import { User, Pagination } from './index'
|
||||||
import Models from './models'
|
import * as Models from './models'
|
||||||
import config from '../../scripts/load-config'
|
import config from '../../scripts/load-config'
|
||||||
|
|
||||||
import { Feed } from 'feed'
|
import { Feed } from 'feed'
|
||||||
@ -13,7 +13,7 @@ function slugify (title) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function cleanArticle (entry, shortenContent = false) {
|
async function cleanArticle (entry, shortenContent = false) {
|
||||||
const poster = await API.User.get(entry.user_id)
|
const poster = await User.get(entry.user_id)
|
||||||
const article = {
|
const article = {
|
||||||
id: entry.id,
|
id: entry.id,
|
||||||
slug: slugify(entry.title),
|
slug: slugify(entry.title),
|
||||||
@ -37,8 +37,7 @@ async function cleanArticle (entry, shortenContent = false) {
|
|||||||
return article
|
return article
|
||||||
}
|
}
|
||||||
|
|
||||||
const News = {
|
export async function preview () {
|
||||||
preview: async () => {
|
|
||||||
// Fetch 3 latest stories
|
// Fetch 3 latest stories
|
||||||
const news = await Models.News.query().orderBy('created_at', 'desc').limit(3)
|
const news = await Models.News.query().orderBy('created_at', 'desc').limit(3)
|
||||||
|
|
||||||
@ -51,8 +50,9 @@ const News = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return articles
|
return articles
|
||||||
},
|
}
|
||||||
listNews: async (page) => {
|
|
||||||
|
export async function listNews (page) {
|
||||||
let count = await Models.News.query().count('id as ids')
|
let count = await Models.News.query().count('id as ids')
|
||||||
if (page < 1) page = 1
|
if (page < 1) page = 1
|
||||||
|
|
||||||
@ -61,7 +61,7 @@ const News = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
count = count[0].ids
|
count = count[0].ids
|
||||||
const paginated = API.Pagination(perPage, parseInt(count), page)
|
const paginated = Pagination(perPage, parseInt(count), page)
|
||||||
const news = await Models.News.query().orderBy('created_at', 'desc').offset(paginated.offset).limit(perPage)
|
const news = await Models.News.query().orderBy('created_at', 'desc').offset(paginated.offset).limit(perPage)
|
||||||
|
|
||||||
const articles = []
|
const articles = []
|
||||||
@ -75,15 +75,17 @@ const News = {
|
|||||||
page: paginated,
|
page: paginated,
|
||||||
articles: articles
|
articles: articles
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
article: async (id) => {
|
|
||||||
|
export async function article (id) {
|
||||||
let article = await Models.News.query().where('id', id)
|
let article = await Models.News.query().where('id', id)
|
||||||
if (!article.length) return {}
|
if (!article.length) return {}
|
||||||
article = article[0]
|
article = article[0]
|
||||||
|
|
||||||
return cleanArticle(article)
|
return cleanArticle(article)
|
||||||
},
|
}
|
||||||
compose: async (user, body) => {
|
|
||||||
|
export async function compose (user, body) {
|
||||||
const article = {
|
const article = {
|
||||||
title: body.title,
|
title: body.title,
|
||||||
content: body.content,
|
content: body.content,
|
||||||
@ -97,8 +99,9 @@ const News = {
|
|||||||
result.slug = slugify(result.title)
|
result.slug = slugify(result.title)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
},
|
}
|
||||||
edit: async (id, body) => {
|
|
||||||
|
export async function edit (id, body) {
|
||||||
const patch = {
|
const patch = {
|
||||||
content: body.content,
|
content: body.content,
|
||||||
updated_at: new Date()
|
updated_at: new Date()
|
||||||
@ -107,8 +110,9 @@ const News = {
|
|||||||
const result = await Models.News.query().patchAndFetchById(id, patch)
|
const result = await Models.News.query().patchAndFetchById(id, patch)
|
||||||
if (!result) throw new Error('Something went wrong.')
|
if (!result) throw new Error('Something went wrong.')
|
||||||
return {}
|
return {}
|
||||||
},
|
}
|
||||||
generateFeed: async () => {
|
|
||||||
|
export async function generateFeed () {
|
||||||
if (feed && new Date(feed.options.updated).getTime() > Date.now() - 3600000) return feed // Update feed hourly
|
if (feed && new Date(feed.options.updated).getTime() > Date.now() - 3600000) return feed // Update feed hourly
|
||||||
|
|
||||||
const posts = await Models.News.query().orderBy('created_at', 'desc').limit(perPage)
|
const posts = await Models.News.query().orderBy('created_at', 'desc').limit(perPage)
|
||||||
@ -143,20 +147,17 @@ const News = {
|
|||||||
|
|
||||||
feed.addItem({
|
feed.addItem({
|
||||||
title: post.title,
|
title: post.title,
|
||||||
id: post.id,
|
|
||||||
link: `${config.server.domain}/news/${post.id}-${slugify(post.title)}`,
|
link: `${config.server.domain}/news/${post.id}-${slugify(post.title)}`,
|
||||||
description: post.description,
|
description: post.description,
|
||||||
content: post.content,
|
content: post.content,
|
||||||
author: [{
|
author: [{
|
||||||
name: post.author.display_name,
|
name: post.author.display_name,
|
||||||
email: post.author.email
|
email: post.author.email,
|
||||||
|
link: config.server.domain
|
||||||
}],
|
}],
|
||||||
date: new Date(post.updated_at)
|
date: new Date(post.updated_at)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return feed
|
return feed
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = News
|
|
||||||
|
@ -1,9 +1,16 @@
|
|||||||
import error from '../error'
|
import {
|
||||||
import response from '../response'
|
InvalidRequest,
|
||||||
import model from '../model'
|
UnsupportedResponseType,
|
||||||
|
InvalidClient,
|
||||||
|
UnauthorizedClient,
|
||||||
|
InvalidScope,
|
||||||
|
AccessDenied
|
||||||
|
} from '../error'
|
||||||
|
import { data as dataResponse } from '../response'
|
||||||
|
import * as model from '../model'
|
||||||
import wrap from '../wrap'
|
import wrap from '../wrap'
|
||||||
|
|
||||||
module.exports = wrap(async (req, res, next) => {
|
export const authorization = wrap(async (req, res, next) => {
|
||||||
let clientId = null
|
let clientId = null
|
||||||
let redirectUri = null
|
let redirectUri = null
|
||||||
let responseType = null
|
let responseType = null
|
||||||
@ -12,26 +19,26 @@ module.exports = wrap(async (req, res, next) => {
|
|||||||
let user = null
|
let user = null
|
||||||
|
|
||||||
if (!req.query.redirect_uri) {
|
if (!req.query.redirect_uri) {
|
||||||
throw new error.InvalidRequest('redirect_uri field is mandatory for authorization endpoint')
|
throw new InvalidRequest('redirect_uri field is mandatory for authorization endpoint')
|
||||||
}
|
}
|
||||||
|
|
||||||
redirectUri = req.query.redirect_uri
|
redirectUri = req.query.redirect_uri
|
||||||
console.debug('Parameter redirect uri is', redirectUri)
|
console.debug('Parameter redirect uri is', redirectUri)
|
||||||
|
|
||||||
if (!req.query.client_id) {
|
if (!req.query.client_id) {
|
||||||
throw new error.InvalidRequest('client_id field is mandatory for authorization endpoint')
|
throw new InvalidRequest('client_id field is mandatory for authorization endpoint')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for client_secret (prevent passing it)
|
// Check for client_secret (prevent passing it)
|
||||||
if (req.query.client_secret) {
|
if (req.query.client_secret) {
|
||||||
throw new error.InvalidRequest('client_secret field should not be passed to the authorization endpoint')
|
throw new InvalidRequest('client_secret field should not be passed to the authorization endpoint')
|
||||||
}
|
}
|
||||||
|
|
||||||
clientId = req.query.client_id
|
clientId = req.query.client_id
|
||||||
console.debug('Parameter client_id is', clientId)
|
console.debug('Parameter client_id is', clientId)
|
||||||
|
|
||||||
if (!req.query.response_type) {
|
if (!req.query.response_type) {
|
||||||
throw new error.InvalidRequest('response_type field is mandatory for authorization endpoint')
|
throw new InvalidRequest('response_type field is mandatory for authorization endpoint')
|
||||||
}
|
}
|
||||||
|
|
||||||
responseType = req.query.response_type
|
responseType = req.query.response_type
|
||||||
@ -52,7 +59,7 @@ module.exports = wrap(async (req, res, next) => {
|
|||||||
grantTypes.push(responseTypes[i])
|
grantTypes.push(responseTypes[i])
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
throw new error.UnsupportedResponseType('Unknown response_type parameter passed')
|
throw new UnsupportedResponseType('Unknown response_type parameter passed')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,28 +70,28 @@ module.exports = wrap(async (req, res, next) => {
|
|||||||
|
|
||||||
// "None" type cannot be combined with others
|
// "None" type cannot be combined with others
|
||||||
if (grantTypes.length > 1 && grantTypes.indexOf('none') !== -1) {
|
if (grantTypes.length > 1 && grantTypes.indexOf('none') !== -1) {
|
||||||
throw new error.InvalidRequest('Grant type "none" cannot be combined with other grant types')
|
throw new InvalidRequest('Grant type "none" cannot be combined with other grant types')
|
||||||
}
|
}
|
||||||
|
|
||||||
console.debug('Parameter grant_type is', grantTypes.join(' '))
|
console.debug('Parameter grant_type is', grantTypes.join(' '))
|
||||||
|
|
||||||
const client = await req.oauth2.model.client.fetchById(clientId)
|
const client = await req.oauth2.model.client.fetchById(clientId)
|
||||||
if (!client) {
|
if (!client) {
|
||||||
throw new error.InvalidClient('Client not found')
|
throw new InvalidClient('Client not found')
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: multiple redirect URI
|
// TODO: multiple redirect URI
|
||||||
if (!req.oauth2.model.client.getRedirectUri(client)) {
|
if (!req.oauth2.model.client.getRedirectUri(client)) {
|
||||||
throw new error.UnsupportedResponseType('The client has not set a redirect uri')
|
throw new UnsupportedResponseType('The client has not set a redirect uri')
|
||||||
} else if (!req.oauth2.model.client.checkRedirectUri(client, redirectUri)) {
|
} else if (!req.oauth2.model.client.checkRedirectUri(client, redirectUri)) {
|
||||||
throw new error.InvalidRequest('Wrong RedirectUri provided')
|
throw new InvalidRequest('Wrong RedirectUri provided')
|
||||||
}
|
}
|
||||||
console.debug('redirect_uri check passed')
|
console.debug('redirect_uri check passed')
|
||||||
|
|
||||||
// The client needs to support all grant types
|
// The client needs to support all grant types
|
||||||
for (const i in grantTypes) {
|
for (const i in grantTypes) {
|
||||||
if (!req.oauth2.model.client.checkGrantType(client, grantTypes[i]) && grantTypes[i] !== 'none') {
|
if (!req.oauth2.model.client.checkGrantType(client, grantTypes[i]) && grantTypes[i] !== 'none') {
|
||||||
throw new error.UnauthorizedClient('This client does not support grant type ' + grantTypes[i])
|
throw new UnauthorizedClient('This client does not support grant type ' + grantTypes[i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
console.debug('Grant type check passed')
|
console.debug('Grant type check passed')
|
||||||
@ -92,16 +99,16 @@ module.exports = wrap(async (req, res, next) => {
|
|||||||
scope = req.oauth2.model.client.transformScope(req.query.scope)
|
scope = req.oauth2.model.client.transformScope(req.query.scope)
|
||||||
scope = req.oauth2.model.client.checkScope(client, scope)
|
scope = req.oauth2.model.client.checkScope(client, scope)
|
||||||
if (!scope) {
|
if (!scope) {
|
||||||
throw new error.InvalidScope('Client does not allow access to this scope')
|
throw new InvalidScope('Client does not allow access to this scope')
|
||||||
}
|
}
|
||||||
console.debug('Scope check passed')
|
console.debug('Scope check passed')
|
||||||
|
|
||||||
user = await req.oauth2.model.user.fetchFromRequest(req)
|
user = await req.oauth2.model.user.fetchFromRequest(req)
|
||||||
if (!user) {
|
if (!user) {
|
||||||
throw new error.InvalidRequest('There is no currently logged in user')
|
throw new InvalidRequest('There is no currently logged in user')
|
||||||
} else {
|
} else {
|
||||||
if (!user.username) {
|
if (!user.username) {
|
||||||
throw new error.Forbidden(user)
|
throw new Forbidden(user)
|
||||||
}
|
}
|
||||||
console.debug('User fetched from request')
|
console.debug('User fetched from request')
|
||||||
}
|
}
|
||||||
@ -122,15 +129,15 @@ module.exports = wrap(async (req, res, next) => {
|
|||||||
|
|
||||||
// Consent pushed, ensure valid session
|
// Consent pushed, ensure valid session
|
||||||
if (req.method === 'POST' && req.session.csrf && !(req.body.csrf && req.body.csrf === req.session.csrf)) {
|
if (req.method === 'POST' && req.session.csrf && !(req.body.csrf && req.body.csrf === req.session.csrf)) {
|
||||||
throw new error.InvalidRequest('Invalid session')
|
throw new InvalidRequest('Invalid session')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save consent
|
// Save consent
|
||||||
if (!consented) {
|
if (!consented) {
|
||||||
if (!req.body || (typeof req.body.decision) === 'undefined') {
|
if (!req.body || (typeof req.body.decision) === 'undefined') {
|
||||||
throw new error.InvalidRequest('No decision parameter passed')
|
throw new InvalidRequest('No decision parameter passed')
|
||||||
} else if (req.body.decision === '0') {
|
} else if (req.body.decision === '0') {
|
||||||
throw new error.AccessDenied('User denied access to the resource')
|
throw new AccessDenied('User denied access to the resource')
|
||||||
}
|
}
|
||||||
console.debug('Decision check passed')
|
console.debug('Decision check passed')
|
||||||
|
|
||||||
@ -161,10 +168,10 @@ module.exports = wrap(async (req, res, next) => {
|
|||||||
resObj = {}
|
resObj = {}
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
throw new error.UnsupportedResponseType('Unknown response_type parameter passed')
|
throw new UnsupportedResponseType('Unknown response_type parameter passed')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return non-code response types as fragment instead of query
|
// Return non-code response types as fragment instead of query
|
||||||
return response.data(req, res, resObj, redirectUri, responseType !== 'code')
|
return dataResponse(req, res, resObj, redirectUri, responseType !== 'code')
|
||||||
}, true)
|
}, true)
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
module.exports = function (req, res, client, scope, user) {
|
export function decision (req, res, client, scope, user) {
|
||||||
res.render('authorization', { client: client, scope: scope })
|
res.render('authorization', { client: client, scope: scope })
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
module.exports = {
|
export * from './authorization';
|
||||||
authorization: require('./authorization'),
|
export * from './introspection';
|
||||||
introspection: require('./introspection'),
|
export * from './token';
|
||||||
token: require('./token')
|
|
||||||
}
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import error from '../error'
|
import { InvalidRequest } from '../error'
|
||||||
import response from '../response'
|
import { data as dataResponse } from '../response'
|
||||||
import wrap from '../wrap'
|
import wrap from '../wrap'
|
||||||
|
|
||||||
module.exports = wrap(async function (req, res) {
|
export const introspection = wrap(async function (req, res) {
|
||||||
let clientId = null
|
let clientId = null
|
||||||
let clientSecret = null
|
let clientSecret = null
|
||||||
|
|
||||||
@ -12,21 +12,21 @@ module.exports = wrap(async function (req, res) {
|
|||||||
console.debug('Client credentials parsed from body parameters ', clientId, clientSecret)
|
console.debug('Client credentials parsed from body parameters ', clientId, clientSecret)
|
||||||
} else {
|
} else {
|
||||||
if (!req.headers || !req.headers.authorization) {
|
if (!req.headers || !req.headers.authorization) {
|
||||||
throw new error.InvalidRequest('No authorization header passed')
|
throw new InvalidRequest('No authorization header passed')
|
||||||
}
|
}
|
||||||
|
|
||||||
let pieces = req.headers.authorization.split(' ', 2)
|
let pieces = req.headers.authorization.split(' ', 2)
|
||||||
if (!pieces || pieces.length !== 2) {
|
if (!pieces || pieces.length !== 2) {
|
||||||
throw new error.InvalidRequest('Authorization header is corrupted')
|
throw new InvalidRequest('Authorization header is corrupted')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pieces[0] !== 'Basic') {
|
if (pieces[0] !== 'Basic') {
|
||||||
throw new error.InvalidRequest('Unsupported authorization method:', pieces[0])
|
throw new InvalidRequest('Unsupported authorization method:', pieces[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
pieces = Buffer.from(pieces[1], 'base64').toString('ascii').split(':', 2)
|
pieces = Buffer.from(pieces[1], 'base64').toString('ascii').split(':', 2)
|
||||||
if (!pieces || pieces.length !== 2) {
|
if (!pieces || pieces.length !== 2) {
|
||||||
throw new error.InvalidRequest('Authorization header has corrupted data')
|
throw new InvalidRequest('Authorization header has corrupted data')
|
||||||
}
|
}
|
||||||
|
|
||||||
clientId = pieces[0]
|
clientId = pieces[0]
|
||||||
@ -35,12 +35,12 @@ module.exports = wrap(async function (req, res) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!req.body.token) {
|
if (!req.body.token) {
|
||||||
throw new error.InvalidRequest('Token not provided in request body')
|
throw new InvalidRequest('Token not provided in request body')
|
||||||
}
|
}
|
||||||
|
|
||||||
const token = await req.oauth2.model.accessToken.fetchByToken(req.body.token)
|
const token = await req.oauth2.model.accessToken.fetchByToken(req.body.token)
|
||||||
if (!token) {
|
if (!token) {
|
||||||
throw new error.InvalidRequest('Token does not exist')
|
throw new InvalidRequest('Token does not exist')
|
||||||
}
|
}
|
||||||
|
|
||||||
const ttl = req.oauth2.model.accessToken.getTTL(token)
|
const ttl = req.oauth2.model.accessToken.getTTL(token)
|
||||||
@ -50,5 +50,5 @@ module.exports = wrap(async function (req, res) {
|
|||||||
expires_in: Math.floor(ttl / 1000)
|
expires_in: Math.floor(ttl / 1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
response.data(req, res, resObj)
|
dataResponse(req, res, resObj)
|
||||||
})
|
})
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import token from './tokens'
|
import * as tokens from './tokens'
|
||||||
import error from '../error'
|
import { InvalidRequest, InvalidClient, UnauthorizedClient } from '../error'
|
||||||
import response from '../response'
|
import { data as dataResponse, error as errorResponse } from '../response'
|
||||||
import wrap from '../wrap'
|
import wrap from '../wrap'
|
||||||
|
|
||||||
module.exports = wrap(async (req, res) => {
|
export const token = wrap(async (req, res) => {
|
||||||
let clientId = null
|
let clientId = null
|
||||||
let clientSecret = null
|
let clientSecret = null
|
||||||
let grantType = null
|
let grantType = null
|
||||||
@ -14,21 +14,21 @@ module.exports = wrap(async (req, res) => {
|
|||||||
console.debug('Client credentials parsed from body parameters', clientId, clientSecret)
|
console.debug('Client credentials parsed from body parameters', clientId, clientSecret)
|
||||||
} else {
|
} else {
|
||||||
if (!req.headers || !req.headers.authorization) {
|
if (!req.headers || !req.headers.authorization) {
|
||||||
throw new error.InvalidRequest('No authorization header passed')
|
throw new InvalidRequest('No authorization header passed')
|
||||||
}
|
}
|
||||||
|
|
||||||
let pieces = req.headers.authorization.split(' ', 2)
|
let pieces = req.headers.authorization.split(' ', 2)
|
||||||
if (!pieces || pieces.length !== 2) {
|
if (!pieces || pieces.length !== 2) {
|
||||||
throw new error.InvalidRequest('Authorization header is corrupted')
|
throw new InvalidRequest('Authorization header is corrupted')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pieces[0] !== 'Basic') {
|
if (pieces[0] !== 'Basic') {
|
||||||
throw new error.InvalidRequest('Unsupported authorization method:', pieces[0])
|
throw new InvalidRequest('Unsupported authorization method:', pieces[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
pieces = Buffer.from(pieces[1], 'base64').toString('ascii').split(':', 2)
|
pieces = Buffer.from(pieces[1], 'base64').toString('ascii').split(':', 2)
|
||||||
if (!pieces || pieces.length !== 2) {
|
if (!pieces || pieces.length !== 2) {
|
||||||
throw new error.InvalidRequest('Authorization header has corrupted data')
|
throw new InvalidRequest('Authorization header has corrupted data')
|
||||||
}
|
}
|
||||||
|
|
||||||
clientId = pieces[0]
|
clientId = pieces[0]
|
||||||
@ -37,7 +37,7 @@ module.exports = wrap(async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!req.body.grant_type) {
|
if (!req.body.grant_type) {
|
||||||
throw new error.InvalidRequest('Request body does not contain grant_type parameter')
|
throw new InvalidRequest('Request body does not contain grant_type parameter')
|
||||||
}
|
}
|
||||||
|
|
||||||
grantType = req.body.grant_type
|
grantType = req.body.grant_type
|
||||||
@ -46,16 +46,16 @@ module.exports = wrap(async (req, res) => {
|
|||||||
const client = await req.oauth2.model.client.fetchById(clientId)
|
const client = await req.oauth2.model.client.fetchById(clientId)
|
||||||
|
|
||||||
if (!client) {
|
if (!client) {
|
||||||
throw new error.InvalidClient('Client not found')
|
throw new InvalidClient('Client not found')
|
||||||
}
|
}
|
||||||
|
|
||||||
const valid = req.oauth2.model.client.checkSecret(client, clientSecret)
|
const valid = req.oauth2.model.client.checkSecret(client, clientSecret)
|
||||||
if (!valid) {
|
if (!valid) {
|
||||||
throw new error.UnauthorizedClient('Invalid client secret')
|
throw new UnauthorizedClient('Invalid client secret')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!req.oauth2.model.client.checkGrantType(client, grantType) && grantType !== 'refresh_token') {
|
if (!req.oauth2.model.client.checkGrantType(client, grantType) && grantType !== 'refresh_token') {
|
||||||
throw new error.UnauthorizedClient('Invalid grant type for the client')
|
throw new UnauthorizedClient('Invalid grant type for the client')
|
||||||
} else {
|
} else {
|
||||||
console.debug('Grant type check passed')
|
console.debug('Grant type check passed')
|
||||||
}
|
}
|
||||||
@ -64,25 +64,25 @@ module.exports = wrap(async (req, res) => {
|
|||||||
try {
|
try {
|
||||||
switch (grantType) {
|
switch (grantType) {
|
||||||
case 'authorization_code':
|
case 'authorization_code':
|
||||||
evt = await token.authorizationCode(req.oauth2, client, req.body.code, req.body.redirect_uri)
|
evt = await tokens.authorizationCode(req.oauth2, client, req.body.code, req.body.redirect_uri)
|
||||||
break
|
break
|
||||||
case 'password':
|
case 'password':
|
||||||
evt = await token.password(req.oauth2, client, req.body.username, req.body.password, req.body.scope)
|
evt = await tokens.password(req.oauth2, client, req.body.username, req.body.password, req.body.scope)
|
||||||
break
|
break
|
||||||
case 'client_credentials':
|
case 'client_credentials':
|
||||||
evt = await token.clientCredentials(req.oauth2, client, req.body.scope)
|
evt = await tokens.clientCredentials(req.oauth2, client, req.body.scope)
|
||||||
break
|
break
|
||||||
case 'refresh_token':
|
case 'refresh_token':
|
||||||
evt = await token.refreshToken(req.oauth2, client, req.body.refresh_token, req.body.scope)
|
evt = await tokens.refreshToken(req.oauth2, client, req.body.refresh_token, req.body.scope)
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
throw new error.UnsupportedGrantType('Grant type does not match any supported type')
|
throw new error.UnsupportedGrantType('Grant type does not match any supported type')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (evt) {
|
if (evt) {
|
||||||
response.data(req, res, evt)
|
dataResponse(req, res, evt)
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
response.error(req, res, e)
|
errorResponse(req, res, e)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import error from '../../error'
|
import { InvalidRequest, ServerError, InvalidGrant } from '../../error'
|
||||||
|
|
||||||
module.exports = async (oauth2, client, providedCode, redirectUri) => {
|
export async function authorizationCode (oauth2, client, providedCode, redirectUri) {
|
||||||
const respObj = {
|
const respObj = {
|
||||||
token_type: 'bearer'
|
token_type: 'bearer'
|
||||||
}
|
}
|
||||||
@ -8,26 +8,26 @@ module.exports = async (oauth2, client, providedCode, redirectUri) => {
|
|||||||
let code = null
|
let code = null
|
||||||
|
|
||||||
if (!providedCode) {
|
if (!providedCode) {
|
||||||
throw new error.InvalidRequest('code is mandatory for authorization_code grant type')
|
throw new InvalidRequest('code is mandatory for authorization_code grant type')
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
code = await oauth2.model.code.fetchByCode(providedCode)
|
code = await oauth2.model.code.fetchByCode(providedCode)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err)
|
console.error(err)
|
||||||
throw new error.ServerError('Failed to call code.fetchByCode function')
|
throw new ServerError('Failed to call code.fetchByCode function')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (code) {
|
if (code) {
|
||||||
if (oauth2.model.code.getClientId(code) !== oauth2.model.client.getId(client)) {
|
if (oauth2.model.code.getClientId(code) !== oauth2.model.client.getId(client)) {
|
||||||
throw new error.InvalidGrant('Code was issued by another client')
|
throw new InvalidGrant('Code was issued by another client')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!oauth2.model.code.checkTTL(code)) {
|
if (!oauth2.model.code.checkTTL(code)) {
|
||||||
throw new error.InvalidGrant('Code has already expired')
|
throw new InvalidGrant('Code has already expired')
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new error.InvalidGrant('Code not found')
|
throw new InvalidGrant('Code not found')
|
||||||
}
|
}
|
||||||
|
|
||||||
console.debug('Code fetched ', code)
|
console.debug('Code fetched ', code)
|
||||||
@ -37,7 +37,7 @@ module.exports = async (oauth2, client, providedCode, redirectUri) => {
|
|||||||
oauth2.model.code.getClientId(code))
|
oauth2.model.code.getClientId(code))
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err)
|
console.error(err)
|
||||||
throw new error.ServerError('Failed to call refreshToken.removeByUserIdClientId function')
|
throw new ServerError('Failed to call refreshToken.removeByUserIdClientId function')
|
||||||
}
|
}
|
||||||
|
|
||||||
console.debug('Refresh token removed')
|
console.debug('Refresh token removed')
|
||||||
@ -50,7 +50,7 @@ module.exports = async (oauth2, client, providedCode, redirectUri) => {
|
|||||||
oauth2.model.code.getClientId(code), oauth2.model.code.getScope(code))
|
oauth2.model.code.getClientId(code), oauth2.model.code.getScope(code))
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err)
|
console.error(err)
|
||||||
throw new error.ServerError('Failed to call refreshToken.create function')
|
throw new ServerError('Failed to call refreshToken.create function')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,7 +59,7 @@ module.exports = async (oauth2, client, providedCode, redirectUri) => {
|
|||||||
oauth2.model.code.getClientId(code), oauth2.model.code.getScope(code), oauth2.model.accessToken.ttl)
|
oauth2.model.code.getClientId(code), oauth2.model.code.getScope(code), oauth2.model.accessToken.ttl)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err)
|
console.error(err)
|
||||||
throw new error.ServerError('Failed to call accessToken.create function')
|
throw new ServerError('Failed to call accessToken.create function')
|
||||||
}
|
}
|
||||||
|
|
||||||
respObj.expires_in = oauth2.model.accessToken.ttl
|
respObj.expires_in = oauth2.model.accessToken.ttl
|
||||||
@ -69,7 +69,7 @@ module.exports = async (oauth2, client, providedCode, redirectUri) => {
|
|||||||
await oauth2.model.code.removeByCode(providedCode)
|
await oauth2.model.code.removeByCode(providedCode)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err)
|
console.error(err)
|
||||||
throw new error.ServerError('Failed to call code.removeByCode function')
|
throw new ServerError('Failed to call code.removeByCode function')
|
||||||
}
|
}
|
||||||
|
|
||||||
return respObj
|
return respObj
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import error from '../../error'
|
import { ServerError, InvalidScope } from '../../error'
|
||||||
|
|
||||||
module.exports = async (oauth2, client, wantScope) => {
|
export async function clientCredentials (oauth2, client, wantScope) {
|
||||||
let scope = null
|
let scope = null
|
||||||
|
|
||||||
const resObj = {
|
const resObj = {
|
||||||
@ -11,7 +11,7 @@ module.exports = async (oauth2, client, wantScope) => {
|
|||||||
scope = oauth2.model.client.checkScope(client, scope)
|
scope = oauth2.model.client.checkScope(client, scope)
|
||||||
|
|
||||||
if (!scope) {
|
if (!scope) {
|
||||||
throw new error.InvalidScope('Client does not allow access to this scope')
|
throw new InvalidScope('Client does not allow access to this scope')
|
||||||
}
|
}
|
||||||
|
|
||||||
console.debug('Scope check passed ', scope)
|
console.debug('Scope check passed ', scope)
|
||||||
@ -20,7 +20,7 @@ module.exports = async (oauth2, client, wantScope) => {
|
|||||||
resObj.access_token = await oauth2.model.accessToken.create(null, oauth2.model.client.getId(client),
|
resObj.access_token = await oauth2.model.accessToken.create(null, oauth2.model.client.getId(client),
|
||||||
scope, oauth2.model.accessToken.ttl)
|
scope, oauth2.model.accessToken.ttl)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new error.ServerError('Failed to call accessToken.create function')
|
throw new ServerError('Failed to call accessToken.create function')
|
||||||
}
|
}
|
||||||
|
|
||||||
resObj.expires_in = oauth2.model.accessToken.ttl
|
resObj.expires_in = oauth2.model.accessToken.ttl
|
||||||
|
@ -1,11 +1,4 @@
|
|||||||
import authorizationCode from './authorizationCode'
|
export * from './authorizationCode'
|
||||||
import clientCredentials from './clientCredentials'
|
export * from './clientCredentials'
|
||||||
import password from './password'
|
export * from './password'
|
||||||
import refreshToken from './refreshToken'
|
export * from './refreshToken'
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
authorizationCode: authorizationCode,
|
|
||||||
clientCredentials: clientCredentials,
|
|
||||||
password: password,
|
|
||||||
refreshToken: refreshToken
|
|
||||||
}
|
|
||||||
|
@ -1,23 +1,23 @@
|
|||||||
import error from '../../error'
|
import { ServerError, InvalidRequest, InvalidScope, InvalidClient } from '../../error'
|
||||||
|
|
||||||
module.exports = async (oauth2, client, username, password, scope) => {
|
export async function password (oauth2, client, username, password, scope) {
|
||||||
let user = null
|
let user = null
|
||||||
const resObj = {
|
const resObj = {
|
||||||
token_type: 'bearer'
|
token_type: 'bearer'
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!username) {
|
if (!username) {
|
||||||
throw new error.InvalidRequest('Username is mandatory for password grant type')
|
throw new InvalidRequest('Username is mandatory for password grant type')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!password) {
|
if (!password) {
|
||||||
throw new error.InvalidRequest('Password is mandatory for password grant type')
|
throw new InvalidRequest('Password is mandatory for password grant type')
|
||||||
}
|
}
|
||||||
|
|
||||||
scope = oauth2.model.client.transformScope(scope)
|
scope = oauth2.model.client.transformScope(scope)
|
||||||
scope = oauth2.model.client.checkScope(client, scope)
|
scope = oauth2.model.client.checkScope(client, scope)
|
||||||
if (!scope) {
|
if (!scope) {
|
||||||
throw new error.InvalidScope('Client does not allow access to this scope')
|
throw new InvalidScope('Client does not allow access to this scope')
|
||||||
} else {
|
} else {
|
||||||
console.debug('Scope check passed: ', scope)
|
console.debug('Scope check passed: ', scope)
|
||||||
}
|
}
|
||||||
@ -25,23 +25,23 @@ module.exports = async (oauth2, client, username, password, scope) => {
|
|||||||
try {
|
try {
|
||||||
user = await oauth2.model.user.fetchByUsername(username)
|
user = await oauth2.model.user.fetchByUsername(username)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new error.ServerError('Failed to call user.fetchByUsername function')
|
throw new ServerError('Failed to call user.fetchByUsername function')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
throw new error.InvalidClient('User not found')
|
throw new InvalidClient('User not found')
|
||||||
}
|
}
|
||||||
|
|
||||||
const valid = await oauth2.model.user.checkPassword(user, password)
|
const valid = await oauth2.model.user.checkPassword(user, password)
|
||||||
if (!valid) {
|
if (!valid) {
|
||||||
throw new error.InvalidClient('Wrong password')
|
throw new InvalidClient('Wrong password')
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await oauth2.model.refreshToken.removeByUserIdClientId(oauth2.model.user.getId(user),
|
await oauth2.model.refreshToken.removeByUserIdClientId(oauth2.model.user.getId(user),
|
||||||
oauth2.model.client.getId(client))
|
oauth2.model.client.getId(client))
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new error.ServerError('Failed to call refreshToken.removeByUserIdClientId function')
|
throw new ServerError('Failed to call refreshToken.removeByUserIdClientId function')
|
||||||
}
|
}
|
||||||
|
|
||||||
console.debug('Refresh token removed')
|
console.debug('Refresh token removed')
|
||||||
@ -53,7 +53,7 @@ module.exports = async (oauth2, client, username, password, scope) => {
|
|||||||
resObj.refresh_token = await oauth2.model.refreshToken.create(oauth2.model.user.getId(user),
|
resObj.refresh_token = await oauth2.model.refreshToken.create(oauth2.model.user.getId(user),
|
||||||
oauth2.model.client.getId(client), scope)
|
oauth2.model.client.getId(client), scope)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new error.ServerError('Failed to call refreshToken.create function')
|
throw new ServerError('Failed to call refreshToken.create function')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,7 +61,7 @@ module.exports = async (oauth2, client, username, password, scope) => {
|
|||||||
resObj.access_token = await oauth2.model.accessToken.create(oauth2.model.user.getId(user),
|
resObj.access_token = await oauth2.model.accessToken.create(oauth2.model.user.getId(user),
|
||||||
oauth2.model.client.getId(client), scope, oauth2.model.accessToken.ttl)
|
oauth2.model.client.getId(client), scope, oauth2.model.accessToken.ttl)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new error.ServerError('Failed to call accessToken.create function')
|
throw new ServerError('Failed to call accessToken.create function')
|
||||||
}
|
}
|
||||||
|
|
||||||
resObj.expires_in = oauth2.model.accessToken.ttl
|
resObj.expires_in = oauth2.model.accessToken.ttl
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import error from '../../error'
|
import { InvalidRequest, ServerError, InvalidGrant, InvalidClient } from '../../error'
|
||||||
|
|
||||||
module.exports = async (oauth2, client, pRefreshToken, scope) => {
|
export async function refreshToken (oauth2, client, pRefreshToken, scope) {
|
||||||
let user = null
|
let user = null
|
||||||
let ttl = null
|
let ttl = null
|
||||||
let refreshToken = null
|
let refreshToken = null
|
||||||
@ -11,40 +11,40 @@ module.exports = async (oauth2, client, pRefreshToken, scope) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!pRefreshToken) {
|
if (!pRefreshToken) {
|
||||||
throw new error.InvalidRequest('refresh_token is mandatory for refresh_token grant type')
|
throw new InvalidRequest('refresh_token is mandatory for refresh_token grant type')
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
refreshToken = await oauth2.model.refreshToken.fetchByToken(pRefreshToken)
|
refreshToken = await oauth2.model.refreshToken.fetchByToken(pRefreshToken)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new error.ServerError('Failed to call refreshToken.fetchByToken function')
|
throw new ServerError('Failed to call refreshToken.fetchByToken function')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!refreshToken) {
|
if (!refreshToken) {
|
||||||
throw new error.InvalidGrant('Refresh token not found')
|
throw new InvalidGrant('Refresh token not found')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (oauth2.model.refreshToken.getClientId(refreshToken) !== oauth2.model.client.getId(client)) {
|
if (oauth2.model.refreshToken.getClientId(refreshToken) !== oauth2.model.client.getId(client)) {
|
||||||
console.warn('Client %s tried to fetch a refresh token which belongs to client %s!', oauth2.model.client.getId(client),
|
console.warn('Client %s tried to fetch a refresh token which belongs to client %s!', oauth2.model.client.getId(client),
|
||||||
oauth2.model.refreshToken.getClientId(refreshToken))
|
oauth2.model.refreshToken.getClientId(refreshToken))
|
||||||
throw new error.InvalidGrant('Refresh token not found')
|
throw new InvalidGrant('Refresh token not found')
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
user = await oauth2.model.user.fetchById(oauth2.model.refreshToken.getUserId(refreshToken))
|
user = await oauth2.model.user.fetchById(oauth2.model.refreshToken.getUserId(refreshToken))
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new error.ServerError('Failed to call user.fetchById function')
|
throw new ServerError('Failed to call user.fetchById function')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
throw new error.InvalidClient('User not found')
|
throw new InvalidClient('User not found')
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
accessToken = await oauth2.model.accessToken.fetchByUserIdClientId(oauth2.model.user.getId(user),
|
accessToken = await oauth2.model.accessToken.fetchByUserIdClientId(oauth2.model.user.getId(user),
|
||||||
oauth2.model.client.getId(client))
|
oauth2.model.client.getId(client))
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new error.ServerError('Failed to call accessToken.fetchByUserIdClientId function')
|
throw new ServerError('Failed to call accessToken.fetchByUserIdClientId function')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (accessToken) {
|
if (accessToken) {
|
||||||
@ -63,7 +63,7 @@ module.exports = async (oauth2, client, pRefreshToken, scope) => {
|
|||||||
resObj.access_token = await oauth2.model.accessToken.create(oauth2.model.user.getId(user),
|
resObj.access_token = await oauth2.model.accessToken.create(oauth2.model.user.getId(user),
|
||||||
oauth2.model.client.getId(client), oauth2.model.refreshToken.getScope(refreshToken), oauth2.model.accessToken.ttl)
|
oauth2.model.client.getId(client), oauth2.model.refreshToken.getScope(refreshToken), oauth2.model.accessToken.ttl)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new error.ServerError('Failed to call accessToken.create function')
|
throw new ServerError('Failed to call accessToken.create function')
|
||||||
}
|
}
|
||||||
|
|
||||||
resObj.expires_in = oauth2.model.accessToken.ttl
|
resObj.expires_in = oauth2.model.accessToken.ttl
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
class OAuth2Error extends Error {
|
export class OAuth2Error extends Error {
|
||||||
constructor (code, msg, status) {
|
constructor (code, msg, status) {
|
||||||
super()
|
super()
|
||||||
Error.captureStackTrace(this, this.constructor)
|
Error.captureStackTrace(this, this.constructor)
|
||||||
@ -12,7 +12,7 @@ class OAuth2Error extends Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class AccessDenied extends OAuth2Error {
|
export class AccessDenied extends OAuth2Error {
|
||||||
constructor (msg) {
|
constructor (msg) {
|
||||||
super('access_denied', msg, 403)
|
super('access_denied', msg, 403)
|
||||||
|
|
||||||
@ -21,7 +21,7 @@ class AccessDenied extends OAuth2Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class InvalidClient extends OAuth2Error {
|
export class InvalidClient extends OAuth2Error {
|
||||||
constructor (msg) {
|
constructor (msg) {
|
||||||
super('invalid_client', msg, 401)
|
super('invalid_client', msg, 401)
|
||||||
|
|
||||||
@ -30,7 +30,7 @@ class InvalidClient extends OAuth2Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class InvalidGrant extends OAuth2Error {
|
export class InvalidGrant extends OAuth2Error {
|
||||||
constructor (msg) {
|
constructor (msg) {
|
||||||
super('invalid_grant', msg, 400)
|
super('invalid_grant', msg, 400)
|
||||||
|
|
||||||
@ -39,7 +39,7 @@ class InvalidGrant extends OAuth2Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class InvalidRequest extends OAuth2Error {
|
export class InvalidRequest extends OAuth2Error {
|
||||||
constructor (msg) {
|
constructor (msg) {
|
||||||
super('invalid_request', msg, 400)
|
super('invalid_request', msg, 400)
|
||||||
|
|
||||||
@ -48,7 +48,7 @@ class InvalidRequest extends OAuth2Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class InvalidScope extends OAuth2Error {
|
export class InvalidScope extends OAuth2Error {
|
||||||
constructor (msg) {
|
constructor (msg) {
|
||||||
super('invalid_scope', msg, 400)
|
super('invalid_scope', msg, 400)
|
||||||
|
|
||||||
@ -57,7 +57,7 @@ class InvalidScope extends OAuth2Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ServerError extends OAuth2Error {
|
export class ServerError extends OAuth2Error {
|
||||||
constructor (msg) {
|
constructor (msg) {
|
||||||
super('server_error', msg, 500)
|
super('server_error', msg, 500)
|
||||||
|
|
||||||
@ -66,7 +66,7 @@ class ServerError extends OAuth2Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class UnauthorizedClient extends OAuth2Error {
|
export class UnauthorizedClient extends OAuth2Error {
|
||||||
constructor (msg) {
|
constructor (msg) {
|
||||||
super('unauthorized_client', msg, 400)
|
super('unauthorized_client', msg, 400)
|
||||||
|
|
||||||
@ -75,7 +75,7 @@ class UnauthorizedClient extends OAuth2Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class UnsupportedGrantType extends OAuth2Error {
|
export class UnsupportedGrantType extends OAuth2Error {
|
||||||
constructor (msg) {
|
constructor (msg) {
|
||||||
super('unsupported_grant_type', msg, 400)
|
super('unsupported_grant_type', msg, 400)
|
||||||
|
|
||||||
@ -84,7 +84,7 @@ class UnsupportedGrantType extends OAuth2Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class UnsupportedResponseType extends OAuth2Error {
|
export class UnsupportedResponseType extends OAuth2Error {
|
||||||
constructor (msg) {
|
constructor (msg) {
|
||||||
super('unsupported_response_type', msg, 400)
|
super('unsupported_response_type', msg, 400)
|
||||||
|
|
||||||
@ -92,16 +92,3 @@ class UnsupportedResponseType extends OAuth2Error {
|
|||||||
this.logLevel = 'info'
|
this.logLevel = 'info'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
OAuth2Error: OAuth2Error,
|
|
||||||
AccessDenied: AccessDenied,
|
|
||||||
InvalidClient: InvalidClient,
|
|
||||||
InvalidGrant: InvalidGrant,
|
|
||||||
InvalidRequest: InvalidRequest,
|
|
||||||
InvalidScope: InvalidScope,
|
|
||||||
ServerError: ServerError,
|
|
||||||
UnauthorizedClient: UnauthorizedClient,
|
|
||||||
UnsupportedGrantType: UnsupportedGrantType,
|
|
||||||
UnsupportedResponseType: UnsupportedResponseType
|
|
||||||
}
|
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import middleware from './middleware'
|
import * as controller from './controller'
|
||||||
import controller from './controller'
|
import { middleware } from './middleware'
|
||||||
import decision from './controller/decision'
|
import { decision } from './controller/decision'
|
||||||
import model from './model'
|
import * as model from './model'
|
||||||
|
|
||||||
class OAuth2Provider {
|
export class OAuth2Provider {
|
||||||
constructor () {
|
constructor () {
|
||||||
this.bearer = middleware
|
this.bearer = middleware
|
||||||
this.controller = controller
|
this.controller = controller
|
||||||
@ -19,5 +19,3 @@ class OAuth2Provider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = OAuth2Provider
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import error from './error'
|
import { AccessDenied, Forbidden } from './error'
|
||||||
import wrap from './wrap'
|
import wrap from './wrap'
|
||||||
|
|
||||||
const middleware = wrap(async function (req, res, next) {
|
export const middleware = wrap(async function (req, res, next) {
|
||||||
console.debug('Parsing bearer token')
|
console.debug('Parsing bearer token')
|
||||||
let token = null
|
let token = null
|
||||||
|
|
||||||
@ -11,12 +11,12 @@ const middleware = wrap(async function (req, res, next) {
|
|||||||
|
|
||||||
// Check authorization header
|
// Check authorization header
|
||||||
if (!pieces || pieces.length !== 2) {
|
if (!pieces || pieces.length !== 2) {
|
||||||
throw new error.AccessDenied('Wrong authorization header')
|
throw new AccessDenied('Wrong authorization header')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only bearer auth is supported
|
// Only bearer auth is supported
|
||||||
if (pieces[0].toLowerCase() !== 'bearer') {
|
if (pieces[0].toLowerCase() !== 'bearer') {
|
||||||
throw new error.AccessDenied('Unsupported authorization method in header')
|
throw new AccessDenied('Unsupported authorization method in header')
|
||||||
}
|
}
|
||||||
|
|
||||||
token = pieces[1]
|
token = pieces[1]
|
||||||
@ -28,20 +28,18 @@ const middleware = wrap(async function (req, res, next) {
|
|||||||
token = req.body.access_token
|
token = req.body.access_token
|
||||||
console.debug('Bearer token parsed from body params:', token)
|
console.debug('Bearer token parsed from body params:', token)
|
||||||
} else {
|
} else {
|
||||||
throw new error.AccessDenied('Bearer token not found')
|
throw new AccessDenied('Bearer token not found')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to fetch access token
|
// Try to fetch access token
|
||||||
const object = await req.oauth2.model.accessToken.fetchByToken(token)
|
const object = await req.oauth2.model.accessToken.fetchByToken(token)
|
||||||
if (!object) {
|
if (!object) {
|
||||||
throw new error.Forbidden('Token not found or has expired')
|
throw new Forbidden('Token not found or has expired')
|
||||||
} else if (!req.oauth2.model.accessToken.checkTTL(object)) {
|
} else if (!req.oauth2.model.accessToken.checkTTL(object)) {
|
||||||
throw new error.Forbidden('Token is expired')
|
throw new Forbidden('Token is expired')
|
||||||
} else {
|
} else {
|
||||||
req.oauth2.accessToken = object
|
req.oauth2.accessToken = object
|
||||||
console.debug('AccessToken fetched', object)
|
console.debug('AccessToken fetched', object)
|
||||||
next()
|
next()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
module.exports = middleware
|
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
import config from '../../../scripts/load-config'
|
import config from '../../../scripts/load-config'
|
||||||
import Models from '../models'
|
import * as Models from '../models'
|
||||||
import Users from '../index'
|
import { User, Login } from '../index'
|
||||||
import crypto from 'crypto'
|
import crypto from 'crypto'
|
||||||
|
|
||||||
const OAuthDB = {
|
export const scopes = {
|
||||||
scopes: {
|
|
||||||
email: ['See your Email address', 'View the user\'s email address'],
|
email: ['See your Email address', 'View the user\'s email address'],
|
||||||
image: ['', 'View the user\'s profile picture'],
|
image: ['', 'View the user\'s profile picture'],
|
||||||
privilege: ['', 'See the user\'s privilege level']
|
privilege: ['', 'See the user\'s privilege level']
|
||||||
},
|
}
|
||||||
accessToken: {
|
|
||||||
|
export const accessToken = {
|
||||||
ttl: config.oauth2.access_token_life,
|
ttl: config.oauth2.access_token_life,
|
||||||
getToken: (object) => {
|
getToken: (object) => {
|
||||||
if (object) return object.token
|
if (object) return object.token
|
||||||
@ -65,34 +65,35 @@ const OAuthDB = {
|
|||||||
|
|
||||||
return tkn[0]
|
return tkn[0]
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
client: {
|
|
||||||
getId: (client) => {
|
export const client = {
|
||||||
return client.id
|
getId: (c) => {
|
||||||
|
return c.id
|
||||||
},
|
},
|
||||||
fetchById: async (id) => {
|
fetchById: async (id) => {
|
||||||
const client = await Models.OAuth2Client.query().where('id', id)
|
const c = await Models.OAuth2Client.query().where('id', id)
|
||||||
|
|
||||||
if (!client.length) return null
|
if (!c.length) return null
|
||||||
|
|
||||||
return client[0]
|
return c[0]
|
||||||
},
|
},
|
||||||
checkSecret: (client, secret) => {
|
checkSecret: (c, secret) => {
|
||||||
return client.secret === secret
|
return c.secret === secret
|
||||||
},
|
},
|
||||||
checkGrantType: (client, grant) => {
|
checkGrantType: (c, grant) => {
|
||||||
if (client.grants.indexOf(grant) !== -1) {
|
if (c.grants.indexOf(grant) !== -1) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
},
|
},
|
||||||
getRedirectUri: (client) => {
|
getRedirectUri: (c) => {
|
||||||
return client.redirect_url
|
return c.redirect_url
|
||||||
},
|
},
|
||||||
checkRedirectUri: (client, redirectUri) => {
|
checkRedirectUri: (c, redirectUri) => {
|
||||||
return (redirectUri.indexOf(OAuthDB.client.getRedirectUri(client)) === 0 &&
|
return (redirectUri.indexOf(client.getRedirectUri(c)) === 0 &&
|
||||||
redirectUri.replace(OAuthDB.client.getRedirectUri(client), '').indexOf('#') === -1)
|
redirectUri.replace(client.getRedirectUri(c), '').indexOf('#') === -1)
|
||||||
},
|
},
|
||||||
transformScope: (scope) => {
|
transformScope: (scope) => {
|
||||||
if (!scope) return []
|
if (!scope) return []
|
||||||
@ -109,13 +110,13 @@ const OAuthDB = {
|
|||||||
|
|
||||||
return scope
|
return scope
|
||||||
},
|
},
|
||||||
checkScope: (client, scope) => {
|
checkScope: (c, scope) => {
|
||||||
if (!scope) return []
|
if (!scope) return []
|
||||||
if (typeof scope === 'string') {
|
if (typeof scope === 'string') {
|
||||||
scope = OAuthDB.client.transformScope(scope)
|
scope = c.transformScope(scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
const clientScopes = client.scope.split(' ')
|
const clientScopes = c.scope.split(' ')
|
||||||
|
|
||||||
for (const i in scope) {
|
for (const i in scope) {
|
||||||
if (clientScopes.indexOf(scope[i]) === -1) {
|
if (clientScopes.indexOf(scope[i]) === -1) {
|
||||||
@ -125,11 +126,12 @@ const OAuthDB = {
|
|||||||
|
|
||||||
return scope
|
return scope
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
code: {
|
|
||||||
|
export const code = {
|
||||||
ttl: config.oauth2.code_life,
|
ttl: config.oauth2.code_life,
|
||||||
create: async (userId, clientId, scope, ttl, special = false) => {
|
create: async (userId, clientId, scope, ttl, special = false) => {
|
||||||
const code = crypto.randomBytes(config.oauth2.token_length).toString('hex')
|
const newCode = crypto.randomBytes(config.oauth2.token_length).toString('hex')
|
||||||
const expr = new Date(Date.now() + ttl * 1000)
|
const expr = new Date(Date.now() + ttl * 1000)
|
||||||
|
|
||||||
if (typeof scope === 'object') {
|
if (typeof scope === 'object') {
|
||||||
@ -141,7 +143,7 @@ const OAuthDB = {
|
|||||||
await Models.OAuth2Code.query().delete().where('user_id', userId).andWhere('client_id', clientId)
|
await Models.OAuth2Code.query().delete().where('user_id', userId).andWhere('client_id', clientId)
|
||||||
|
|
||||||
const obj = {
|
const obj = {
|
||||||
code,
|
code: newCode,
|
||||||
scope,
|
scope,
|
||||||
user_id: userId,
|
user_id: userId,
|
||||||
client_id: clientId,
|
client_id: clientId,
|
||||||
@ -153,34 +155,35 @@ const OAuthDB = {
|
|||||||
|
|
||||||
return obj.code
|
return obj.code
|
||||||
},
|
},
|
||||||
fetchByCode: async (code) => {
|
fetchByCode: async (c) => {
|
||||||
code = await Models.OAuth2Code.query().where('code', code)
|
c = await Models.OAuth2Code.query().where('code', c)
|
||||||
|
|
||||||
if (!code.length) return null
|
if (!c.length) return null
|
||||||
|
|
||||||
return code[0]
|
return c[0]
|
||||||
},
|
},
|
||||||
removeByCode: async (code) => {
|
removeByCode: async (c) => {
|
||||||
if (typeof code === 'object') {
|
if (typeof c === 'object') {
|
||||||
code = code.code
|
c = c.code
|
||||||
}
|
}
|
||||||
|
|
||||||
return Models.OAuth2Code.query().delete().where('code', code)
|
return Models.OAuth2Code.query().delete().where('code', c)
|
||||||
},
|
},
|
||||||
getUserId: (code) => {
|
getUserId: (c) => {
|
||||||
return code.user_id
|
return c.user_id
|
||||||
},
|
},
|
||||||
getClientId: (code) => {
|
getClientId: (c) => {
|
||||||
return code.client_id
|
return c.client_id
|
||||||
},
|
},
|
||||||
getScope: (code) => {
|
getScope: (c) => {
|
||||||
return code.scope
|
return c.scope
|
||||||
},
|
},
|
||||||
checkTTL: (code) => {
|
checkTTL: (c) => {
|
||||||
return (code.expires_at > Date.now())
|
return (c.expires_at > Date.now())
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
refreshToken: {
|
|
||||||
|
export const refreshToken = {
|
||||||
create: async (userId, clientId, scope) => {
|
create: async (userId, clientId, scope) => {
|
||||||
const token = crypto.randomBytes(config.oauth2.token_length).toString('hex')
|
const token = crypto.randomBytes(config.oauth2.token_length).toString('hex')
|
||||||
|
|
||||||
@ -214,26 +217,27 @@ const OAuthDB = {
|
|||||||
removeByRefreshToken: async (token) => {
|
removeByRefreshToken: async (token) => {
|
||||||
return Models.OAuth2RefreshToken.query().delete().where('token', token)
|
return Models.OAuth2RefreshToken.query().delete().where('token', token)
|
||||||
},
|
},
|
||||||
getUserId: (refreshToken) => {
|
getUserId: (t) => {
|
||||||
return refreshToken.user_id
|
return t.user_id
|
||||||
},
|
},
|
||||||
getClientId: (refreshToken) => {
|
getClientId: (t) => {
|
||||||
return refreshToken.client_id
|
return t.client_id
|
||||||
},
|
},
|
||||||
getScope: (refreshToken) => {
|
getScope: (t) => {
|
||||||
return refreshToken.scope
|
return t.scope
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const user = {
|
||||||
|
getId: (u) => {
|
||||||
|
return u.id
|
||||||
},
|
},
|
||||||
user: {
|
fetchById: User.get,
|
||||||
getId: (user) => {
|
fetchByUsername: User.get,
|
||||||
return user.id
|
checkPassword: Login.password,
|
||||||
},
|
|
||||||
fetchById: Users.User.get,
|
|
||||||
fetchByUsername: Users.User.get,
|
|
||||||
checkPassword: Users.User.Login.password,
|
|
||||||
fetchFromRequest: async (req) => {
|
fetchFromRequest: async (req) => {
|
||||||
if (!req.session.user) return null
|
if (!req.session.user) return null
|
||||||
const banStatus = await Users.User.getBanStatus(req.session.user.id)
|
const banStatus = await User.getBanStatus(req.session.user.id)
|
||||||
|
|
||||||
if (banStatus.length) {
|
if (banStatus.length) {
|
||||||
delete req.session.user
|
delete req.session.user
|
||||||
@ -287,7 +291,4 @@ const OAuthDB = {
|
|||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = OAuthDB
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import query from 'querystring'
|
import query from 'querystring'
|
||||||
import error from './error'
|
import { OAuth2Error, ServerError } from './error'
|
||||||
|
|
||||||
function data (req, res, code, data) {
|
function dataRes (req, res, code, data) {
|
||||||
res.header('Cache-Control', 'no-store')
|
res.header('Cache-Control', 'no-store')
|
||||||
res.header('Pragma', 'no-cache')
|
res.header('Pragma', 'no-cache')
|
||||||
res.status(code).send(data)
|
res.status(code).send(data)
|
||||||
@ -14,11 +14,11 @@ function redirect (req, res, redirectUri) {
|
|||||||
console.debug('Redirecting to ', redirectUri)
|
console.debug('Redirecting to ', redirectUri)
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports.error = function (req, res, err, redirectUri) {
|
export function error (req, res, err, redirectUri) {
|
||||||
// Transform unknown error
|
// Transform unknown error
|
||||||
if (!(err instanceof error.OAuth2Error)) {
|
if (!(err instanceof OAuth2Error)) {
|
||||||
console.error(err.stack)
|
console.error(err.stack)
|
||||||
err = new error.ServerError('Uncaught exception')
|
err = new ServerError('Uncaught exception')
|
||||||
} else {
|
} else {
|
||||||
console.error('Exception caught', err.stack)
|
console.error('Exception caught', err.stack)
|
||||||
}
|
}
|
||||||
@ -36,11 +36,11 @@ module.exports.error = function (req, res, err, redirectUri) {
|
|||||||
redirectUri += '?' + query.stringify(obj)
|
redirectUri += '?' + query.stringify(obj)
|
||||||
redirect(req, res, redirectUri)
|
redirect(req, res, redirectUri)
|
||||||
} else {
|
} else {
|
||||||
data(req, res, err.status, { error: err.code, error_description: err.message })
|
dataRes(req, res, err.status, { error: err.code, error_description: err.message })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports.data = function (req, res, obj, redirectUri, fragment) {
|
export function data (req, res, obj, redirectUri, fragment) {
|
||||||
if (redirectUri) {
|
if (redirectUri) {
|
||||||
if (fragment) {
|
if (fragment) {
|
||||||
redirectUri += '#'
|
redirectUri += '#'
|
||||||
@ -55,6 +55,6 @@ module.exports.data = function (req, res, obj, redirectUri, fragment) {
|
|||||||
redirectUri += query.stringify(obj)
|
redirectUri += query.stringify(obj)
|
||||||
redirect(req, res, redirectUri)
|
redirect(req, res, redirectUri)
|
||||||
} else {
|
} else {
|
||||||
data(req, res, 200, obj)
|
dataRes(req, res, 200, obj)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import response from './response'
|
import { error } from './response'
|
||||||
|
|
||||||
module.exports = (fn, redir) => (req, res, next) =>
|
export default (fn, redir) => (req, res, next) =>
|
||||||
fn(req, res, next).catch(e =>
|
fn(req, res, next).catch(e =>
|
||||||
response.error(req, res, e, redir ? req.query.redirect_uri : null))
|
error(req, res, e, redir ? req.query.redirect_uri : null))
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import express from 'express'
|
import express from 'express'
|
||||||
|
|
||||||
import ensureLogin from '../../scripts/ensureLogin'
|
import { ensureLogin } from '../../scripts/ensureLogin'
|
||||||
import wrap from '../../scripts/asyncRoute'
|
import wrap from '../../scripts/asyncRoute'
|
||||||
import API from '../api/admin'
|
import * as API from '../api/admin'
|
||||||
import Emailer from '../api/emailer'
|
import { sendMail } from '../api/emailer'
|
||||||
import { User } from '../api'
|
import { User, Login } from '../api'
|
||||||
|
|
||||||
const router = express.Router()
|
const router = express.Router()
|
||||||
const apiRouter = express.Router()
|
const apiRouter = express.Router()
|
||||||
@ -46,7 +46,7 @@ router.post('/', wrap(async (req, res, next) => {
|
|||||||
return next()
|
return next()
|
||||||
}
|
}
|
||||||
|
|
||||||
const passReady = await User.Login.password(req.session.user, req.body.password)
|
const passReady = await Login.password(req.session.user, req.body.password)
|
||||||
if (passReady) {
|
if (passReady) {
|
||||||
req.session.accesstime = Date.now() + 600000 // 10 minutes
|
req.session.accesstime = Date.now() + 600000 // 10 minutes
|
||||||
return res.redirect('/admin')
|
return res.redirect('/admin')
|
||||||
@ -299,7 +299,7 @@ apiRouter.post('/email', csrfVerify, wrap(async (req, res) => {
|
|||||||
html: req.body.content
|
html: req.body.content
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await Emailer.sendMail(req.body.email, message)
|
const result = await sendMail(req.body.email, message)
|
||||||
|
|
||||||
res.jsonp(result)
|
res.jsonp(result)
|
||||||
}))
|
}))
|
||||||
@ -311,4 +311,4 @@ apiRouter.use((err, req, res, next) => {
|
|||||||
|
|
||||||
router.use('/api', apiRouter)
|
router.use('/api', apiRouter)
|
||||||
|
|
||||||
module.exports = router
|
export default router
|
||||||
|
@ -4,10 +4,10 @@ import multiparty from 'multiparty'
|
|||||||
|
|
||||||
import config from '../../scripts/load-config'
|
import config from '../../scripts/load-config'
|
||||||
import wrap from '../../scripts/asyncRoute'
|
import wrap from '../../scripts/asyncRoute'
|
||||||
import APIExtern from '../api/external'
|
import { Common, Facebook, Twitter, Discord, Google } from '../api/external'
|
||||||
import Image from '../api/image'
|
import * as Image from '../api/image'
|
||||||
import News from '../api/news'
|
import * as News from '../api/news'
|
||||||
import API from '../api'
|
import { User, Payment, OAuth2 } from '../api'
|
||||||
|
|
||||||
const router = express.Router()
|
const router = express.Router()
|
||||||
const dev = process.env.NODE_ENV !== 'production'
|
const dev = process.env.NODE_ENV !== 'production'
|
||||||
@ -84,7 +84,7 @@ function JsonData (req, res, error, redirect = '/') {
|
|||||||
function removeAuthMiddleware (identifier) {
|
function removeAuthMiddleware (identifier) {
|
||||||
return wrap(async (req, res) => {
|
return wrap(async (req, res) => {
|
||||||
if (!req.session.user) return res.redirect('/login')
|
if (!req.session.user) return res.redirect('/login')
|
||||||
const done = await APIExtern.Common.remove(req.session.user, identifier)
|
const done = await Common.remove(req.session.user, identifier)
|
||||||
|
|
||||||
if (!done) {
|
if (!done) {
|
||||||
req.flash('message', { error: true, text: 'Unable to unlink social media account' })
|
req.flash('message', { error: true, text: 'Unable to unlink social media account' })
|
||||||
@ -107,7 +107,7 @@ router.post('/external/facebook/callback', wrap(async (req, res, next) => {
|
|||||||
return next()
|
return next()
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await APIExtern.Facebook.callback(req.session.user, sane.authResponse, req.realIP)
|
const response = await Facebook.callback(req.session.user, sane.authResponse, req.realIP)
|
||||||
|
|
||||||
if (response.banned) {
|
if (response.banned) {
|
||||||
return JsonData(req, res, 'You are banned.')
|
return JsonData(req, res, 'You are banned.')
|
||||||
@ -134,7 +134,7 @@ router.get('/external/facebook/remove', removeAuthMiddleware('facebook'))
|
|||||||
*/
|
*/
|
||||||
router.get('/external/twitter/login', wrap(async (req, res) => {
|
router.get('/external/twitter/login', wrap(async (req, res) => {
|
||||||
if (!config.external || !config.external.twitter || !config.external.twitter.api) return res.redirect('/')
|
if (!config.external || !config.external.twitter || !config.external.twitter.api) return res.redirect('/')
|
||||||
const tokens = await APIExtern.Twitter.getRequestToken()
|
const tokens = await Twitter.getRequestToken()
|
||||||
|
|
||||||
if (tokens.error) {
|
if (tokens.error) {
|
||||||
return res.jsonp({ error: tokens.error })
|
return res.jsonp({ error: tokens.error })
|
||||||
@ -156,7 +156,7 @@ router.get('/external/twitter/callback', wrap(async (req, res) => {
|
|||||||
return res.redirect(uri)
|
return res.redirect(uri)
|
||||||
}
|
}
|
||||||
|
|
||||||
const accessTokens = await APIExtern.Twitter.getAccessTokens(ta.token, ta.token_secret, req.query.oauth_verifier)
|
const accessTokens = await Twitter.getAccessTokens(ta.token, ta.token_secret, req.query.oauth_verifier)
|
||||||
delete req.session.twitter_auth
|
delete req.session.twitter_auth
|
||||||
|
|
||||||
if (accessTokens.error) {
|
if (accessTokens.error) {
|
||||||
@ -164,7 +164,7 @@ router.get('/external/twitter/callback', wrap(async (req, res) => {
|
|||||||
return res.redirect(uri)
|
return res.redirect(uri)
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await APIExtern.Twitter.callback(req.session.user, accessTokens, req.realIP)
|
const response = await Twitter.callback(req.session.user, accessTokens, req.realIP)
|
||||||
if (response.banned) {
|
if (response.banned) {
|
||||||
return res.render('user/banned', { bans: response.banned, ipban: response.ip })
|
return res.render('user/banned', { bans: response.banned, ipban: response.ip })
|
||||||
}
|
}
|
||||||
@ -191,7 +191,7 @@ router.get('/external/twitter/remove', removeAuthMiddleware('twitter'))
|
|||||||
router.get('/external/discord/login', wrap(async (req, res) => {
|
router.get('/external/discord/login', wrap(async (req, res) => {
|
||||||
if (!config.external || !config.external.discord || !config.external.discord.api) return res.redirect('/')
|
if (!config.external || !config.external.discord || !config.external.discord.api) return res.redirect('/')
|
||||||
|
|
||||||
const infos = APIExtern.Discord.getAuthorizeURL(req)
|
const infos = Discord.getAuthorizeURL(req)
|
||||||
|
|
||||||
res.redirect(infos.url)
|
res.redirect(infos.url)
|
||||||
}))
|
}))
|
||||||
@ -208,20 +208,20 @@ router.get('/external/discord/callback', wrap(async (req, res) => {
|
|||||||
return res.redirect(uri)
|
return res.redirect(uri)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!state || state !== APIExtern.Common.stateGenerator(req)) {
|
if (!state || state !== Common.stateGenerator(req)) {
|
||||||
req.flash('message', { error: true, text: 'Request got intercepted, try again.' })
|
req.flash('message', { error: true, text: 'Request got intercepted, try again.' })
|
||||||
return res.redirect(uri)
|
return res.redirect(uri)
|
||||||
}
|
}
|
||||||
|
|
||||||
delete req.session.discord_auth
|
delete req.session.discord_auth
|
||||||
|
|
||||||
const accessToken = await APIExtern.Discord.getAccessToken(code)
|
const accessToken = await Discord.getAccessToken(code)
|
||||||
if (accessToken.error) {
|
if (accessToken.error) {
|
||||||
req.flash('message', { error: true, text: accessToken.error })
|
req.flash('message', { error: true, text: accessToken.error })
|
||||||
return res.redirect(uri)
|
return res.redirect(uri)
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await APIExtern.Discord.callback(req.session.user, accessToken.accessToken, req.realIP)
|
const response = await Discord.callback(req.session.user, accessToken.accessToken, req.realIP)
|
||||||
if (response.banned) {
|
if (response.banned) {
|
||||||
return res.render('user/banned', { bans: response.banned, ipban: response.ip })
|
return res.render('user/banned', { bans: response.banned, ipban: response.ip })
|
||||||
}
|
}
|
||||||
@ -258,7 +258,7 @@ router.post('/external/google/callback', wrap(async (req, res) => {
|
|||||||
return JsonData(req, res, 'Invalid or missing ID token!', '/login')
|
return JsonData(req, res, 'Invalid or missing ID token!', '/login')
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await APIExtern.Google.callback(req.session.user, req.body, req.realIP)
|
const response = await Google.callback(req.session.user, req.body, req.realIP)
|
||||||
if (response.banned) {
|
if (response.banned) {
|
||||||
return JsonData(req, res, 'You are banned.', '/login')
|
return JsonData(req, res, 'You are banned.', '/login')
|
||||||
}
|
}
|
||||||
@ -371,7 +371,7 @@ router.post('/avatar', uploadLimiter, wrap(async (req, res, next) => {
|
|||||||
try {
|
try {
|
||||||
const result = await Image.uploadImage(req.session.user.username, data.fields, data.files)
|
const result = await Image.uploadImage(req.session.user.username, data.fields, data.files)
|
||||||
|
|
||||||
avatarFile = await API.User.changeAvatar(req.session.user, result.file)
|
avatarFile = await User.changeAvatar(req.session.user, result.file)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return res.status(400).jsonp({ error: e.message })
|
return res.status(400).jsonp({ error: e.message })
|
||||||
}
|
}
|
||||||
@ -388,7 +388,7 @@ router.post('/avatar', uploadLimiter, wrap(async (req, res, next) => {
|
|||||||
router.post('/avatar/remove', wrap(async (req, res, next) => {
|
router.post('/avatar/remove', wrap(async (req, res, next) => {
|
||||||
if (!req.session.user) return next()
|
if (!req.session.user) return next()
|
||||||
|
|
||||||
await API.User.removeAvatar(req.session.user)
|
await User.removeAvatar(req.session.user)
|
||||||
req.session.user.avatar_file = null
|
req.session.user.avatar_file = null
|
||||||
|
|
||||||
res.status(200).jsonp({ done: true })
|
res.status(200).jsonp({ done: true })
|
||||||
@ -410,7 +410,7 @@ router.get('/avatar/:id', wrap(async (req, res, next) => {
|
|||||||
const id = idParam(req)
|
const id = idParam(req)
|
||||||
if (!id) return next()
|
if (!id) return next()
|
||||||
|
|
||||||
const user = await API.User.get(id)
|
const user = await User.get(id)
|
||||||
|
|
||||||
if (!user || !user.avatar_file) return next()
|
if (!user || !user.avatar_file) return next()
|
||||||
|
|
||||||
@ -432,12 +432,13 @@ router.post('/avatar/gravatar', wrap(async (req, res, next) => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const gravURL = await Image.downloadImage(Image.gravatarURL(user.email), 'GRAV-' + user.username)
|
const gravURL = await Image.downloadImage(Image.gravatarURL(user.email), 'GRAV-' + user.username)
|
||||||
const file = await API.User.changeAvatar(user, gravURL)
|
const file = await User.changeAvatar(user, gravURL)
|
||||||
|
|
||||||
req.session.user.avatar_file = file
|
req.session.user.avatar_file = file
|
||||||
|
|
||||||
req.flash('message', { error: false, text: 'Success!' })
|
req.flash('message', { error: false, text: 'Success!' })
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
req.flash('message', { error: true, text: 'Failed to use gravatar avatar.' })
|
req.flash('message', { error: true, text: 'Failed to use gravatar avatar.' })
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -458,7 +459,7 @@ router.use('/avatar', (req, res) => {
|
|||||||
router.get('/oauth2/authorized-clients', wrap(async (req, res, next) => {
|
router.get('/oauth2/authorized-clients', wrap(async (req, res, next) => {
|
||||||
if (!req.session.user) return next()
|
if (!req.session.user) return next()
|
||||||
|
|
||||||
const list = await API.User.OAuth2.getUserAuthorizations(req.session.user)
|
const list = await OAuth2.getUserAuthorizations(req.session.user)
|
||||||
if (!list) return next()
|
if (!list) return next()
|
||||||
|
|
||||||
res.jsonp(list)
|
res.jsonp(list)
|
||||||
@ -471,7 +472,7 @@ router.post('/oauth2/authorized-clients/revoke', wrap(async (req, res, next) =>
|
|||||||
const clientId = parseInt(req.body.client_id)
|
const clientId = parseInt(req.body.client_id)
|
||||||
if (isNaN(clientId)) return res.status(400).jsonp({ error: 'Missing Client ID parameter' })
|
if (isNaN(clientId)) return res.status(400).jsonp({ error: 'Missing Client ID parameter' })
|
||||||
|
|
||||||
const done = await API.User.OAuth2.removeUserAuthorization(req.session.user, clientId)
|
const done = await OAuth2.removeUserAuthorization(req.session.user, clientId)
|
||||||
if (!done) return res.status(400).jsonp({ error: 'Failed to remove client authorization' })
|
if (!done) return res.status(400).jsonp({ error: 'Failed to remove client authorization' })
|
||||||
|
|
||||||
res.status(204).end()
|
res.status(204).end()
|
||||||
@ -486,7 +487,7 @@ router.post('/paypal/ipn', wrap(async (req, res) => {
|
|||||||
const content = req.body
|
const content = req.body
|
||||||
|
|
||||||
if (content && content.payment_status && content.payment_status === 'Completed') {
|
if (content && content.payment_status && content.payment_status === 'Completed') {
|
||||||
await API.Payment.handleIPN(content)
|
await Payment.handleIPN(content)
|
||||||
}
|
}
|
||||||
|
|
||||||
res.status(204).end()
|
res.status(204).end()
|
||||||
@ -495,7 +496,7 @@ router.post('/paypal/ipn', wrap(async (req, res) => {
|
|||||||
router.get('/donations/user', wrap(async (req, res, next) => {
|
router.get('/donations/user', wrap(async (req, res, next) => {
|
||||||
if (!req.session.user) return next()
|
if (!req.session.user) return next()
|
||||||
|
|
||||||
const contribs = await API.Payment.userContributions(req.session.user)
|
const contribs = await Payment.userContributions(req.session.user)
|
||||||
res.jsonp(contribs)
|
res.jsonp(contribs)
|
||||||
}))
|
}))
|
||||||
|
|
||||||
@ -513,7 +514,7 @@ router.get('/donations', wrap(async (req, res, next) => {
|
|||||||
let timeFrame = parseInt(req.query.timeFrame)
|
let timeFrame = parseInt(req.query.timeFrame)
|
||||||
if (isNaN(timeFrame)) timeFrame = 0
|
if (isNaN(timeFrame)) timeFrame = 0
|
||||||
|
|
||||||
const contribs = await API.Payment.allContributions(count, mcu, timeFrame)
|
const contribs = await Payment.allContributions(count, mcu, timeFrame)
|
||||||
res.jsonp(contribs)
|
res.jsonp(contribs)
|
||||||
}))
|
}))
|
||||||
|
|
||||||
@ -527,4 +528,4 @@ router.use((err, req, res, next) => {
|
|||||||
res.jsonp({ error: 'Internal server error.' })
|
res.jsonp({ error: 'Internal server error.' })
|
||||||
})
|
})
|
||||||
|
|
||||||
module.exports = router
|
export default router
|
||||||
|
@ -3,13 +3,13 @@ import path from 'path'
|
|||||||
import express from 'express'
|
import express from 'express'
|
||||||
import RateLimit from 'express-rate-limit'
|
import RateLimit from 'express-rate-limit'
|
||||||
|
|
||||||
import ensureLogin from '../../scripts/ensureLogin'
|
import { ensureLogin } from '../../scripts/ensureLogin'
|
||||||
import config from '../../scripts/load-config'
|
import config from '../../scripts/load-config'
|
||||||
import wrap from '../../scripts/asyncRoute'
|
import wrap from '../../scripts/asyncRoute'
|
||||||
import http from '../../scripts/http'
|
import { httpPOST } from '../../scripts/http'
|
||||||
import API from '../api'
|
import { User, Login, Reset, Register } from '../api'
|
||||||
import News from '../api/news'
|
import * as News from '../api/news'
|
||||||
import emailer from '../api/emailer'
|
import { pushMail } from '../api/emailer'
|
||||||
|
|
||||||
import apiRouter from './api'
|
import apiRouter from './api'
|
||||||
import oauthRouter from './oauth2'
|
import oauthRouter from './oauth2'
|
||||||
@ -70,7 +70,7 @@ router.use(wrap(async (req, res, next) => {
|
|||||||
console.debug('User session update')
|
console.debug('User session update')
|
||||||
|
|
||||||
// Check for bans
|
// Check for bans
|
||||||
const banStatus = await API.User.getBanStatus(req.session.user.id)
|
const banStatus = await User.getBanStatus(req.session.user.id)
|
||||||
|
|
||||||
if (banStatus.length) {
|
if (banStatus.length) {
|
||||||
delete req.session.user
|
delete req.session.user
|
||||||
@ -78,11 +78,11 @@ router.use(wrap(async (req, res, next) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update user session
|
// Update user session
|
||||||
const udata = await API.User.get(req.session.user)
|
const udata = await User.get(req.session.user)
|
||||||
setSession(req, udata)
|
setSession(req, udata)
|
||||||
|
|
||||||
// Update IP address
|
// Update IP address
|
||||||
await API.User.update(udata, { ip_address: req.realIP })
|
await User.update(udata, { ip_address: req.realIP })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -156,7 +156,7 @@ router.get('/login/reset', extraButtons(false), (req, res) => {
|
|||||||
router.get('/reset/:token', wrap(async (req, res) => {
|
router.get('/reset/:token', wrap(async (req, res) => {
|
||||||
if (req.session.user) return res.redirect('/login')
|
if (req.session.user) return res.redirect('/login')
|
||||||
const token = req.params.token
|
const token = req.params.token
|
||||||
const success = await API.User.Reset.resetToken(token)
|
const success = await Reset.resetToken(token)
|
||||||
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
req.flash('message', { error: true, text: 'Invalid or expired reset token.' })
|
req.flash('message', { error: true, text: 'Invalid or expired reset token.' })
|
||||||
@ -167,7 +167,7 @@ router.get('/reset/:token', wrap(async (req, res) => {
|
|||||||
res.render('user/password_new', { token: true })
|
res.render('user/password_new', { token: true })
|
||||||
}))
|
}))
|
||||||
|
|
||||||
router.get('/login', extraButtons(false), (req, res) => {
|
router.get('/login', extraButtons(false), formKeep, (req, res) => {
|
||||||
if (req.session.user) return redirectLogin(req, res)
|
if (req.session.user) return redirectLogin(req, res)
|
||||||
|
|
||||||
if (req.query.returnTo) {
|
if (req.query.returnTo) {
|
||||||
@ -191,7 +191,7 @@ router.get('/register', extraButtons(true), formKeep, (req, res) => {
|
|||||||
router.get('/activate/:token', wrap(async (req, res) => {
|
router.get('/activate/:token', wrap(async (req, res) => {
|
||||||
if (req.session.user) return res.redirect('/login')
|
if (req.session.user) return res.redirect('/login')
|
||||||
const token = req.params.token
|
const token = req.params.token
|
||||||
const success = await API.User.Login.activationToken(token)
|
const success = await Login.activationToken(token)
|
||||||
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
req.flash('message', { error: true, text: 'Invalid or expired activation token.' })
|
req.flash('message', { error: true, text: 'Invalid or expired activation token.' })
|
||||||
@ -204,10 +204,10 @@ router.get('/activate/:token', wrap(async (req, res) => {
|
|||||||
|
|
||||||
// View for enabling Two-Factor Authentication
|
// View for enabling Two-Factor Authentication
|
||||||
router.get('/user/two-factor', ensureLogin, wrap(async (req, res) => {
|
router.get('/user/two-factor', ensureLogin, wrap(async (req, res) => {
|
||||||
const twoFaEnabled = await API.User.Login.totpTokenRequired(req.session.user)
|
const twoFaEnabled = await Login.totpTokenRequired(req.session.user)
|
||||||
if (twoFaEnabled) return res.redirect('/')
|
if (twoFaEnabled) return res.redirect('/')
|
||||||
|
|
||||||
const newToken = await API.User.Login.totpAquire(req.session.user)
|
const newToken = await Login.totpAquire(req.session.user)
|
||||||
if (!newToken) return res.redirect('/')
|
if (!newToken) return res.redirect('/')
|
||||||
|
|
||||||
res.render('user/totp', { uri: newToken })
|
res.render('user/totp', { uri: newToken })
|
||||||
@ -215,7 +215,7 @@ router.get('/user/two-factor', ensureLogin, wrap(async (req, res) => {
|
|||||||
|
|
||||||
// View for disabling Two-Factor Authentication
|
// View for disabling Two-Factor Authentication
|
||||||
router.get('/user/two-factor/disable', ensureLogin, wrap(async (req, res) => {
|
router.get('/user/two-factor/disable', ensureLogin, wrap(async (req, res) => {
|
||||||
const twoFaEnabled = await API.User.Login.totpTokenRequired(req.session.user)
|
const twoFaEnabled = await Login.totpTokenRequired(req.session.user)
|
||||||
|
|
||||||
if (!twoFaEnabled) return res.redirect('/')
|
if (!twoFaEnabled) return res.redirect('/')
|
||||||
res.render('user/password')
|
res.render('user/password')
|
||||||
@ -229,10 +229,10 @@ router.get('/login/verify', (req, res) => {
|
|||||||
// User settings page
|
// User settings page
|
||||||
router.get('/user/manage', ensureLogin, wrap(async (req, res) => {
|
router.get('/user/manage', ensureLogin, wrap(async (req, res) => {
|
||||||
let totpEnabled = false
|
let totpEnabled = false
|
||||||
const socialStatus = await API.User.socialStatus(req.session.user)
|
const socialStatus = await User.socialStatus(req.session.user)
|
||||||
|
|
||||||
if (socialStatus.password) {
|
if (socialStatus.password) {
|
||||||
totpEnabled = await API.User.Login.totpTokenRequired(req.session.user)
|
totpEnabled = await Login.totpTokenRequired(req.session.user)
|
||||||
}
|
}
|
||||||
|
|
||||||
const et = config.external
|
const et = config.external
|
||||||
@ -277,7 +277,7 @@ router.get('/user/manage', ensureLogin, wrap(async (req, res) => {
|
|||||||
|
|
||||||
// Change password
|
// Change password
|
||||||
router.get('/user/manage/password', ensureLogin, wrap(async (req, res) => {
|
router.get('/user/manage/password', ensureLogin, wrap(async (req, res) => {
|
||||||
const socialStatus = await API.User.socialStatus(req.session.user)
|
const socialStatus = await User.socialStatus(req.session.user)
|
||||||
|
|
||||||
res.render('user/password_new', { token: !socialStatus.password })
|
res.render('user/password_new', { token: !socialStatus.password })
|
||||||
}))
|
}))
|
||||||
@ -291,7 +291,7 @@ router.get('/user/manage/email', ensureLogin, wrap(async (req, res) => {
|
|||||||
obfuscated = rep + '@' + split[1]
|
obfuscated = rep + '@' + split[1]
|
||||||
}
|
}
|
||||||
|
|
||||||
const socialStatus = await API.User.socialStatus(req.session.user)
|
const socialStatus = await User.socialStatus(req.session.user)
|
||||||
|
|
||||||
res.render('user/email_change', { email: obfuscated, password: socialStatus.password })
|
res.render('user/email_change', { email: obfuscated, password: socialStatus.password })
|
||||||
}))
|
}))
|
||||||
@ -350,7 +350,7 @@ router.post('/user/two-factor', csrfValidation, wrap(async (req, res, next) => {
|
|||||||
return formError(req, res, 'You need to enter the code.')
|
return formError(req, res, 'You need to enter the code.')
|
||||||
}
|
}
|
||||||
|
|
||||||
const verified = await API.User.Login.totpCheck(req.session.user, req.body.code)
|
const verified = await Login.totpCheck(req.session.user, req.body.code)
|
||||||
if (!verified) {
|
if (!verified) {
|
||||||
return formError(req, res, 'Something went wrong! Try scanning the code again.')
|
return formError(req, res, 'Something went wrong! Try scanning the code again.')
|
||||||
}
|
}
|
||||||
@ -366,7 +366,7 @@ router.post('/user/two-factor/disable', csrfValidation, wrap(async (req, res, ne
|
|||||||
return formError(req, res, 'Please enter your password.')
|
return formError(req, res, 'Please enter your password.')
|
||||||
}
|
}
|
||||||
|
|
||||||
const purge = await API.User.Login.purgeTotp(req.session.user, req.body.password)
|
const purge = await Login.purgeTotp(req.session.user, req.body.password)
|
||||||
if (!purge) {
|
if (!purge) {
|
||||||
return formError(req, res, 'Invalid password.')
|
return formError(req, res, 'Invalid password.')
|
||||||
}
|
}
|
||||||
@ -384,12 +384,12 @@ router.post('/login/verify', csrfValidation, wrap(async (req, res, next) => {
|
|||||||
return formError(req, res, 'You need to enter the code.')
|
return formError(req, res, 'You need to enter the code.')
|
||||||
}
|
}
|
||||||
|
|
||||||
const totpCheck = await API.User.Login.totpCheck(req.session.totp_check, req.body.code, req.body.recovery || false)
|
const totpCheck = await Login.totpCheck(req.session.totp_check, req.body.code, req.body.recovery || false)
|
||||||
if (!totpCheck) {
|
if (!totpCheck) {
|
||||||
return formError(req, res, 'Invalid code!')
|
return formError(req, res, 'Invalid code!')
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = await API.User.get(req.session.totp_check)
|
const user = await User.get(req.session.totp_check)
|
||||||
delete req.session.totp_check
|
delete req.session.totp_check
|
||||||
|
|
||||||
setSession(req, user)
|
setSession(req, user)
|
||||||
@ -404,12 +404,12 @@ router.post('/login', accountLimiter, csrfValidation, wrap(async (req, res, next
|
|||||||
return res.redirect('/login')
|
return res.redirect('/login')
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = await API.User.get(req.body.username)
|
const user = await User.get(req.body.username)
|
||||||
if (!user) return formError(req, res, 'Invalid username or password.')
|
if (!user) return formError(req, res, 'Invalid username or password.')
|
||||||
|
|
||||||
if (!user.password || user.password === '') return formError(req, res, 'Please log in using the buttons on the right.')
|
if (!user.password || user.password === '') return formError(req, res, 'Please log in using the buttons on the right.')
|
||||||
|
|
||||||
const pwMatch = await API.User.Login.password(user, req.body.password)
|
const pwMatch = await Login.password(user, req.body.password)
|
||||||
if (!pwMatch) return formError(req, res, 'Invalid username or password.')
|
if (!pwMatch) return formError(req, res, 'Invalid username or password.')
|
||||||
|
|
||||||
if (user.activated === 0) return formError(req, res, 'Please activate your account first. If you did not receive an email, please contact an administrator.')
|
if (user.activated === 0) return formError(req, res, 'Please activate your account first. If you did not receive an email, please contact an administrator.')
|
||||||
@ -417,13 +417,13 @@ router.post('/login', accountLimiter, csrfValidation, wrap(async (req, res, next
|
|||||||
if (user.locked === 1) return formError(req, res, 'This account has been locked. Please contact an administrator for more information.')
|
if (user.locked === 1) return formError(req, res, 'This account has been locked. Please contact an administrator for more information.')
|
||||||
|
|
||||||
// Check if the user is banned
|
// Check if the user is banned
|
||||||
const banStatus = await API.User.getBanStatus(user.id)
|
const banStatus = await User.getBanStatus(user.id)
|
||||||
if (banStatus.length) {
|
if (banStatus.length) {
|
||||||
return res.render('user/banned', { bans: banStatus, ipban: false })
|
return res.render('user/banned', { bans: banStatus, ipban: false })
|
||||||
}
|
}
|
||||||
|
|
||||||
// Redirect to the verification dialog if 2FA is enabled
|
// Redirect to the verification dialog if 2FA is enabled
|
||||||
const totpRequired = await API.User.Login.totpTokenRequired(user)
|
const totpRequired = await Login.totpTokenRequired(user)
|
||||||
if (totpRequired) {
|
if (totpRequired) {
|
||||||
req.session.totp_check = user.id
|
req.session.totp_check = user.id
|
||||||
return res.redirect('/login/verify')
|
return res.redirect('/login/verify')
|
||||||
@ -454,13 +454,13 @@ router.post('/login/reset', accountLimiter, csrfValidation, wrap(async (req, res
|
|||||||
}
|
}
|
||||||
|
|
||||||
const email = req.body.email
|
const email = req.body.email
|
||||||
const validEmail = await API.User.Register.validateEmail(email)
|
const validEmail = await Register.validateEmail(email)
|
||||||
if (!validEmail) {
|
if (!validEmail) {
|
||||||
return formError(req, res, 'You need to enter a valid email address.')
|
return formError(req, res, 'You need to enter a valid email address.')
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await API.User.Reset.reset(email, false)
|
await Reset.reset(email, false)
|
||||||
|
|
||||||
req.flash('message', { error: false, text: 'We\'ve sent a link to your email address. Please check spam folders, too!' })
|
req.flash('message', { error: false, text: 'We\'ve sent a link to your email address. Please check spam folders, too!' })
|
||||||
res.redirect('/login/reset?success=true')
|
res.redirect('/login/reset?success=true')
|
||||||
@ -473,7 +473,7 @@ router.post('/login/reset', accountLimiter, csrfValidation, wrap(async (req, res
|
|||||||
router.post('/reset/:token', csrfValidation, wrap(async (req, res) => {
|
router.post('/reset/:token', csrfValidation, wrap(async (req, res) => {
|
||||||
if (req.session.user) return res.redirect('/login')
|
if (req.session.user) return res.redirect('/login')
|
||||||
const token = req.params.token
|
const token = req.params.token
|
||||||
const user = await API.User.Reset.resetToken(token)
|
const user = await Reset.resetToken(token)
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
req.flash('message', { error: true, text: 'Invalid or expired reset token.' })
|
req.flash('message', { error: true, text: 'Invalid or expired reset token.' })
|
||||||
@ -494,7 +494,7 @@ router.post('/reset/:token', csrfValidation, wrap(async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await API.User.Reset.changePassword(user, password, token)
|
await Reset.changePassword(user, password, token)
|
||||||
|
|
||||||
console.warn('[SECURITY AUDIT] User \'%s\' password has been changed from %s', user.username, req.realIP)
|
console.warn('[SECURITY AUDIT] User \'%s\' password has been changed from %s', user.username, req.realIP)
|
||||||
|
|
||||||
@ -514,7 +514,7 @@ router.post('/register', accountLimiter, csrfValidation, wrap(async (req, res, n
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Ban check
|
// Ban check
|
||||||
const banStatus = await API.User.getBanStatus(req.realIP, true)
|
const banStatus = await User.getBanStatus(req.realIP, true)
|
||||||
if (banStatus.length) {
|
if (banStatus.length) {
|
||||||
return res.render('user/banned', { bans: banStatus, ipban: true })
|
return res.render('user/banned', { bans: banStatus, ipban: true })
|
||||||
}
|
}
|
||||||
@ -533,7 +533,7 @@ router.post('/register', accountLimiter, csrfValidation, wrap(async (req, res, n
|
|||||||
|
|
||||||
// 3rd Check: Email Address
|
// 3rd Check: Email Address
|
||||||
const email = req.body.email
|
const email = req.body.email
|
||||||
if (!email || !API.User.Register.validateEmail(email)) {
|
if (!email || !Register.validateEmail(email)) {
|
||||||
return formError(req, res, 'Invalid email address!')
|
return formError(req, res, 'Invalid email address!')
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -554,7 +554,7 @@ router.post('/register', accountLimiter, csrfValidation, wrap(async (req, res, n
|
|||||||
if (!req.body['g-recaptcha-response']) return formError(req, res, 'Please complete the reCAPTCHA!')
|
if (!req.body['g-recaptcha-response']) return formError(req, res, 'Please complete the reCAPTCHA!')
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let data = await http.POST('https://www.google.com/recaptcha/api/siteverify', {}, {
|
let data = await httpPOST('https://www.google.com/recaptcha/api/siteverify', {}, {
|
||||||
secret: config.security.recaptcha.secret_key,
|
secret: config.security.recaptcha.secret_key,
|
||||||
response: req.body['g-recaptcha-response']
|
response: req.body['g-recaptcha-response']
|
||||||
})
|
})
|
||||||
@ -570,12 +570,12 @@ router.post('/register', accountLimiter, csrfValidation, wrap(async (req, res, n
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Hash the password
|
// Hash the password
|
||||||
const hash = await API.User.Register.hashPassword(password)
|
const hash = await Register.hashPassword(password)
|
||||||
let newUser
|
let newUser
|
||||||
|
|
||||||
// Attempt to create the user
|
// Attempt to create the user
|
||||||
try {
|
try {
|
||||||
newUser = await API.User.Register.newAccount({
|
newUser = await Register.newAccount({
|
||||||
username: username,
|
username: username,
|
||||||
display_name: cleanString(displayName),
|
display_name: cleanString(displayName),
|
||||||
password: hash,
|
password: hash,
|
||||||
@ -617,7 +617,7 @@ router.post('/user/manage', csrfValidation, wrap(async (req, res, next) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await API.User.update(req.session.user, {
|
await User.update(req.session.user, {
|
||||||
display_name: displayName
|
display_name: displayName
|
||||||
})
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -634,14 +634,14 @@ router.post('/user/manage', csrfValidation, wrap(async (req, res, next) => {
|
|||||||
router.post('/user/manage/password', accountLimiter, csrfValidation, wrap(async (req, res, next) => {
|
router.post('/user/manage/password', accountLimiter, csrfValidation, wrap(async (req, res, next) => {
|
||||||
if (!req.session.user) return next()
|
if (!req.session.user) return next()
|
||||||
const user = req.session.user
|
const user = req.session.user
|
||||||
const socialStatus = await API.User.socialStatus(user)
|
const socialStatus = await User.socialStatus(user)
|
||||||
|
|
||||||
if (!req.body.password_old && socialStatus.password) {
|
if (!req.body.password_old && socialStatus.password) {
|
||||||
return formError(req, res, 'Please enter your current password.')
|
return formError(req, res, 'Please enter your current password.')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (socialStatus.password) {
|
if (socialStatus.password) {
|
||||||
const passwordMatch = await API.User.Login.password(user, req.body.password_old)
|
const passwordMatch = await Login.password(user, req.body.password_old)
|
||||||
if (!passwordMatch) {
|
if (!passwordMatch) {
|
||||||
return formError(req, res, 'The password you provided is incorrect.')
|
return formError(req, res, 'The password you provided is incorrect.')
|
||||||
}
|
}
|
||||||
@ -657,10 +657,10 @@ router.post('/user/manage/password', accountLimiter, csrfValidation, wrap(async
|
|||||||
return formError(req, res, 'The passwords do not match!')
|
return formError(req, res, 'The passwords do not match!')
|
||||||
}
|
}
|
||||||
|
|
||||||
password = await API.User.Register.hashPassword(password)
|
password = await Register.hashPassword(password)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await API.User.update(user, {
|
await User.update(user, {
|
||||||
password: password
|
password: password
|
||||||
})
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -670,7 +670,7 @@ router.post('/user/manage/password', accountLimiter, csrfValidation, wrap(async
|
|||||||
console.warn('[SECURITY AUDIT] User \'%s\' password has been changed from %s', user.username, req.realIP)
|
console.warn('[SECURITY AUDIT] User \'%s\' password has been changed from %s', user.username, req.realIP)
|
||||||
|
|
||||||
if (config.email && config.email.enabled) {
|
if (config.email && config.email.enabled) {
|
||||||
await emailer.pushMail('password_alert', user.email, {
|
await pushMail('password_alert', user.email, {
|
||||||
display_name: user.display_name,
|
display_name: user.display_name,
|
||||||
ip: req.realIP
|
ip: req.realIP
|
||||||
})
|
})
|
||||||
@ -684,7 +684,7 @@ router.post('/user/manage/password', accountLimiter, csrfValidation, wrap(async
|
|||||||
router.post('/user/manage/email', accountLimiter, csrfValidation, wrap(async (req, res, next) => {
|
router.post('/user/manage/email', accountLimiter, csrfValidation, wrap(async (req, res, next) => {
|
||||||
if (!req.session.user) return next()
|
if (!req.session.user) return next()
|
||||||
|
|
||||||
const user = await API.User.get(req.session.user)
|
const user = await User.get(req.session.user)
|
||||||
const email = req.body.email
|
const email = req.body.email
|
||||||
const newEmail = req.body.email_new
|
const newEmail = req.body.email_new
|
||||||
const password = req.body.password
|
const password = req.body.password
|
||||||
@ -702,24 +702,24 @@ router.post('/user/manage/email', accountLimiter, csrfValidation, wrap(async (re
|
|||||||
return formError(req, res, 'Enter a password.')
|
return formError(req, res, 'Enter a password.')
|
||||||
}
|
}
|
||||||
|
|
||||||
const passwordMatch = await API.User.Login.password(user, password)
|
const passwordMatch = await Login.password(user, password)
|
||||||
if (!passwordMatch) {
|
if (!passwordMatch) {
|
||||||
return formError(req, res, 'The password you provided is incorrect.')
|
return formError(req, res, 'The password you provided is incorrect.')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const emailValid = API.User.Register.validateEmail(newEmail)
|
const emailValid = Register.validateEmail(newEmail)
|
||||||
if (!emailValid) {
|
if (!emailValid) {
|
||||||
return formError(req, res, 'Invalid email address.')
|
return formError(req, res, 'Invalid email address.')
|
||||||
}
|
}
|
||||||
|
|
||||||
const emailTaken = await API.User.get(newEmail)
|
const emailTaken = await User.get(newEmail)
|
||||||
if (emailTaken) {
|
if (emailTaken) {
|
||||||
return formError(req, res, 'This email is already taken.')
|
return formError(req, res, 'This email is already taken.')
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await API.User.update(user, {
|
await User.update(user, {
|
||||||
email: newEmail
|
email: newEmail
|
||||||
})
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -883,4 +883,4 @@ router.use((err, req, res, next) => {
|
|||||||
next()
|
next()
|
||||||
})
|
})
|
||||||
|
|
||||||
module.exports = router
|
export default router
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import express from 'express'
|
import express from 'express'
|
||||||
|
|
||||||
import UAPI from '../api'
|
import { User } from '../api'
|
||||||
import OAuth2 from '../api/oauth2'
|
import { OAuth2Provider } from '../api/oauth2'
|
||||||
import RateLimit from 'express-rate-limit'
|
import RateLimit from 'express-rate-limit'
|
||||||
import wrap from '../../scripts/asyncRoute'
|
import wrap from '../../scripts/asyncRoute'
|
||||||
import config from '../../scripts/load-config.js'
|
import config from '../../scripts/load-config.js'
|
||||||
|
|
||||||
const router = express.Router()
|
const router = express.Router()
|
||||||
const oauth = new OAuth2()
|
const oauth = new OAuth2Provider()
|
||||||
|
|
||||||
router.use(oauth.express())
|
router.use(oauth.express())
|
||||||
|
|
||||||
@ -33,7 +33,7 @@ router.post('/introspect', oauth.controller.introspection)
|
|||||||
// Protected user information resource
|
// Protected user information resource
|
||||||
router.get('/user', oauth.bearer, wrap(async (req, res) => {
|
router.get('/user', oauth.bearer, wrap(async (req, res) => {
|
||||||
const accessToken = req.oauth2.accessToken
|
const accessToken = req.oauth2.accessToken
|
||||||
const user = await UAPI.User.get(accessToken.user_id)
|
const user = await User.get(accessToken.user_id)
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return res.status(404).jsonp({
|
return res.status(404).jsonp({
|
||||||
@ -75,4 +75,4 @@ router.use((err, req, res, next) => {
|
|||||||
next()
|
next()
|
||||||
})
|
})
|
||||||
|
|
||||||
module.exports = router
|
export default router
|
||||||
|
@ -10,7 +10,7 @@ import path from 'path'
|
|||||||
import routes from './routes'
|
import routes from './routes'
|
||||||
import flash from '../scripts/flash'
|
import flash from '../scripts/flash'
|
||||||
import config from '../scripts/load-config'
|
import config from '../scripts/load-config'
|
||||||
import email from './api/emailer'
|
import { init as initEmail } from './api/emailer'
|
||||||
|
|
||||||
const app = express()
|
const app = express()
|
||||||
const SessionStore = connectSession(session)
|
const SessionStore = connectSession(session)
|
||||||
@ -95,6 +95,6 @@ module.exports = (args) => {
|
|||||||
console.log('Listening on 0.0.0.0:' + args.port)
|
console.log('Listening on 0.0.0.0:' + args.port)
|
||||||
|
|
||||||
// Initialize the email transporter (if configured)
|
// Initialize the email transporter (if configured)
|
||||||
email.init()
|
initEmail()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ block body
|
|||||||
input(type="hidden", name="csrf", value=csrf)
|
input(type="hidden", name="csrf", value=csrf)
|
||||||
.form-group
|
.form-group
|
||||||
label(for="username") Username or Email Address
|
label(for="username") Username or Email Address
|
||||||
input.form-control(type="text", name="username", id="username", autofocus)
|
input.form-control(type="text", name="username", id="username", value=formkeep.username, autofocus)
|
||||||
.form-group
|
.form-group
|
||||||
label(for="password") Password
|
label(for="password") Password
|
||||||
input.form-control(type="password", name="password", id="password")
|
input.form-control(type="password", name="password", id="password")
|
||||||
|
@ -13,6 +13,9 @@ module.exports = {
|
|||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
vue$: 'vue/dist/vue.esm.js' // 'vue/dist/vue.common.js' for webpack 1
|
vue$: 'vue/dist/vue.esm.js' // 'vue/dist/vue.common.js' for webpack 1
|
||||||
|
},
|
||||||
|
fallback: {
|
||||||
|
querystring: require.resolve('querystring-es3')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
module: {
|
module: {
|
||||||
|
Reference in New Issue
Block a user