Major refactor and code modernization, vol. 1
This commit is contained in:
parent
229a7db04a
commit
444f8b00f8
7366
package-lock.json
generated
7366
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",
|
||||
"dependencies": {
|
||||
"@babel/plugin-transform-modules-commonjs": "^7.10.4",
|
||||
"@babel/register": "^7.10.4",
|
||||
"@babel/plugin-transform-modules-commonjs": "^7.12.1",
|
||||
"@babel/register": "^7.12.10",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"bluebird": "^3.7.2",
|
||||
"body-parser": "^1.19.0",
|
||||
"connect-redis": "^4.0.4",
|
||||
"connect-session-knex": "^1.7.3",
|
||||
"email-templates": "^7.0.5",
|
||||
"connect-redis": "^5.0.0",
|
||||
"connect-session-knex": "^2.0.0",
|
||||
"email-templates": "^8.0.2",
|
||||
"express": "^4.17.1",
|
||||
"express-rate-limit": "^5.1.3",
|
||||
"express-rate-limit": "^5.2.3",
|
||||
"express-session": "^1.17.1",
|
||||
"feed": "^4.2.1",
|
||||
"fs-extra": "^9.0.1",
|
||||
"gm": "^1.23.1",
|
||||
"knex": "^0.21.1",
|
||||
"multiparty": "^4.2.1",
|
||||
"knex": "^0.21.13",
|
||||
"multiparty": "^4.2.2",
|
||||
"mysql": "^2.18.1",
|
||||
"nodemailer": "^6.4.10",
|
||||
"nodemailer": "^6.4.17",
|
||||
"notp": "^2.0.3",
|
||||
"oauth-libre": "^0.9.17",
|
||||
"objection": "^2.2.1",
|
||||
"objection": "^2.2.3",
|
||||
"querystring-es3": "^0.2.1",
|
||||
"redis": "^3.0.2",
|
||||
"serve-favicon": "^2.5.0",
|
||||
"stylus": "^0.54.7",
|
||||
"stylus": "^0.54.8",
|
||||
"thirty-two": "^1.0.2",
|
||||
"toml": "^3.0.0",
|
||||
"uuid": "^8.2.0",
|
||||
"vue": "^2.6.11"
|
||||
"uuid": "^8.3.2",
|
||||
"vue": "^2.6.12"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.10.4",
|
||||
"@babel/preset-env": "^7.10.4",
|
||||
"babel-loader": "^8.1.0",
|
||||
"bootstrap": "^4.5.0",
|
||||
"concurrently": "^5.2.0",
|
||||
"eslint-plugin-import": "^2.22.0",
|
||||
"@babel/core": "^7.12.10",
|
||||
"@babel/preset-env": "^7.12.10",
|
||||
"babel-loader": "^8.2.2",
|
||||
"bootstrap": "^4.5.3",
|
||||
"concurrently": "^5.3.0",
|
||||
"eslint-plugin-import": "^2.22.1",
|
||||
"jquery": "^3.5.1",
|
||||
"morgan": "^1.10.0",
|
||||
"mustache": "^4.0.1",
|
||||
"mustache": "^4.1.0",
|
||||
"popper.js": "^1.16.1",
|
||||
"pug": "^3.0.0",
|
||||
"pug-plain-loader": "^1.0.0",
|
||||
"standard": "^14.3.4",
|
||||
"terser-webpack-plugin": "^3.0.6",
|
||||
"pug-plain-loader": "^1.1.0",
|
||||
"standard": "^16.0.3",
|
||||
"terser-webpack-plugin": "^5.0.3",
|
||||
"vue-clickaway": "^2.2.2",
|
||||
"vue-loader": "^15.9.3",
|
||||
"vue-loader": "^15.9.5",
|
||||
"vue-resource": "^1.5.1",
|
||||
"vue-router": "^3.3.4",
|
||||
"vue-template-compiler": "^2.6.11",
|
||||
"vue-router": "^3.4.9",
|
||||
"vue-template-compiler": "^2.6.12",
|
||||
"watch": "^1.0.2",
|
||||
"webpack": "^4.43.0",
|
||||
"webpack-cli": "^3.3.12",
|
||||
"webpack-merge": "^5.0.7"
|
||||
"webpack": "^5.10.1",
|
||||
"webpack-cli": "^4.2.0",
|
||||
"webpack-merge": "^5.7.0"
|
||||
},
|
||||
"standard": {
|
||||
"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
|
||||
// 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()
|
||||
req.session.redirectUri = req.originalUrl
|
||||
res.redirect('/login')
|
||||
}
|
||||
|
||||
module.exports = ensureLogin
|
||||
|
@ -6,7 +6,7 @@ const format = util.format
|
||||
* Included here to avoid includng ridiculously small modules
|
||||
*/
|
||||
|
||||
module.exports = function (options) {
|
||||
export default function (options) {
|
||||
options = options || {}
|
||||
const safe = (options.unsafe === undefined) ? true : !options.unsafe
|
||||
|
||||
|
@ -2,7 +2,7 @@ import { URL } from 'url'
|
||||
import qs from 'querystring'
|
||||
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
|
||||
const parsed = new URL(link)
|
||||
const opts = {
|
||||
@ -32,7 +32,7 @@ function HTTP_GET (link, headers = {}, lback) {
|
||||
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 = ''
|
||||
@ -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)
|
||||
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) => {
|
||||
const file = fs.createWriteStream(dest)
|
||||
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 = {
|
||||
initialize: function () {
|
||||
// Create log file write stream
|
||||
if (!config.logger || !config.logger.write) return
|
||||
initializeLogger().catch((e) => console.error(e))
|
||||
}
|
||||
export function initialize () {
|
||||
// Create log file write stream
|
||||
if (!config.logger || !config.logger.write) return
|
||||
initializeLogger().catch((e) => console.error(e))
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
import Users from './index'
|
||||
import Models from './models'
|
||||
import { User, Pagination, Hash, Login, Reset, Register } from './index'
|
||||
import * as Models from './models'
|
||||
|
||||
const perPage = 6
|
||||
|
||||
async function cleanUserObject (dbe, admin) {
|
||||
const totp = await Users.User.Login.totpTokenRequired(dbe)
|
||||
const totp = await Login.totpTokenRequired(dbe)
|
||||
|
||||
return {
|
||||
id: dbe.id,
|
||||
@ -25,7 +25,7 @@ async function cleanUserObject (dbe, admin) {
|
||||
}
|
||||
|
||||
async function cleanClientObject (dbe) {
|
||||
const user = await Users.User.get(dbe.user_id)
|
||||
const user = await User.get(dbe.user_id)
|
||||
return {
|
||||
id: dbe.id,
|
||||
title: dbe.title,
|
||||
@ -46,8 +46,8 @@ async function cleanClientObject (dbe) {
|
||||
}
|
||||
|
||||
async function cleanBanObject (dbe) {
|
||||
const user = await Users.User.get(dbe.user_id)
|
||||
const admin = await Users.User.get(dbe.admin_id)
|
||||
const user = await User.get(dbe.user_id)
|
||||
const admin = await User.get(dbe.admin_id)
|
||||
return {
|
||||
id: dbe.id,
|
||||
reason: dbe.reason,
|
||||
@ -82,257 +82,269 @@ function dataFilter (data, fields, optional = []) {
|
||||
return data
|
||||
}
|
||||
|
||||
const API = {
|
||||
// List all users (paginated)
|
||||
getAllUsers: async function (page, adminId) {
|
||||
let count = await Models.User.query().count('id as ids')
|
||||
if (!count.length || !count[0].ids || isNaN(page)) {
|
||||
return { error: 'No users found in database' }
|
||||
}
|
||||
// List all users (paginated)
|
||||
export async function getAllUsers (page, adminId) {
|
||||
let count = await Models.User.query().count('id as ids')
|
||||
if (!count.length || !count[0].ids || isNaN(page)) {
|
||||
return { error: 'No users found in database' }
|
||||
}
|
||||
|
||||
count = count[0].ids
|
||||
const paginated = Users.Pagination(perPage, parseInt(count), page)
|
||||
const raw = await Models.User.query().offset(paginated.offset).limit(perPage)
|
||||
const admin = await Users.User.get(adminId)
|
||||
count = count[0].ids
|
||||
const paginated = Pagination(perPage, parseInt(count), page)
|
||||
const raw = await Models.User.query().offset(paginated.offset).limit(perPage)
|
||||
const admin = await User.get(adminId)
|
||||
|
||||
const users = []
|
||||
for (const i in raw) {
|
||||
const entry = raw[i]
|
||||
const users = []
|
||||
for (const i in raw) {
|
||||
const entry = raw[i]
|
||||
|
||||
users.push(await cleanUserObject(entry, admin))
|
||||
}
|
||||
users.push(await cleanUserObject(entry, admin))
|
||||
}
|
||||
|
||||
return {
|
||||
page: paginated,
|
||||
users: users
|
||||
}
|
||||
},
|
||||
getUser: async function (id) {
|
||||
const user = await Users.User.get(id)
|
||||
if (!user) throw new Error('No such user')
|
||||
|
||||
return cleanUserObject(user, null)
|
||||
},
|
||||
editUser: async function (id, data) {
|
||||
const user = await Users.User.get(id)
|
||||
if (!user) throw new Error('No such user')
|
||||
|
||||
const fields = [
|
||||
'username', 'display_name', 'email', 'nw_privilege', 'activated'
|
||||
]
|
||||
|
||||
data = dataFilter(data, fields, ['nw_privilege', 'activated'])
|
||||
if (!data) throw new Error('Missing fields')
|
||||
|
||||
await Users.User.update(user, data)
|
||||
|
||||
return {}
|
||||
},
|
||||
resendActivationEmail: async function (id) {
|
||||
const user = await Users.User.get(id)
|
||||
if (!user) throw new Error('No such user')
|
||||
|
||||
if (user.activated === 1) return {}
|
||||
|
||||
await Users.User.Register.activationEmail(user)
|
||||
|
||||
return {}
|
||||
},
|
||||
revokeTotpToken: async function (id) {
|
||||
const user = await Users.User.get(id)
|
||||
if (!user) throw new Error('No such user')
|
||||
|
||||
await Models.TotpToken.query().delete().where('user_id', user.id)
|
||||
|
||||
return {}
|
||||
},
|
||||
sendPasswordEmail: async function (id) {
|
||||
const user = await Users.User.get(id)
|
||||
if (!user) throw new Error('No such user')
|
||||
|
||||
const token = await Users.User.Reset.reset(user.email, false, true)
|
||||
|
||||
return { token }
|
||||
},
|
||||
// Search for users by terms and fields
|
||||
searchUsers: async function (terms, fields = ['email']) {
|
||||
let qb = Models.User.query()
|
||||
|
||||
terms = terms.replace(/_/g, '\\_').replace(/%/g, '\\%')
|
||||
|
||||
qb = qb.where(fields[0], 'like', '%' + terms + '%')
|
||||
if (fields.length >= 1) {
|
||||
for (let i = 1; i < fields.length; i++) {
|
||||
qb = qb.orWhere(fields[i], 'like', '%' + terms + '%')
|
||||
}
|
||||
}
|
||||
|
||||
const rows = await qb.limit(8)
|
||||
if (!rows.length) return { error: 'No results' }
|
||||
|
||||
const cleaned = []
|
||||
for (const i in rows) {
|
||||
const userRaw = rows[i]
|
||||
cleaned.push(await cleanUserObject(userRaw, null))
|
||||
}
|
||||
|
||||
return cleaned
|
||||
},
|
||||
// List all clients (paginated)
|
||||
getAllClients: async function (page) {
|
||||
let count = await Models.OAuth2Client.query().count('id as ids')
|
||||
if (!count.length || !count[0].ids || isNaN(page)) {
|
||||
return { error: 'No clients' }
|
||||
}
|
||||
|
||||
count = count[0].ids
|
||||
const paginated = Users.Pagination(perPage, parseInt(count), page)
|
||||
const raw = await Models.OAuth2Client.query().offset(paginated.offset).limit(perPage)
|
||||
|
||||
const clients = []
|
||||
for (const i in raw) {
|
||||
const entry = raw[i]
|
||||
|
||||
clients.push(await cleanClientObject(entry))
|
||||
}
|
||||
|
||||
return {
|
||||
page: paginated, clients
|
||||
}
|
||||
},
|
||||
// Get information about a client via id
|
||||
getClient: async function (id) {
|
||||
const raw = await Models.OAuth2Client.query().where('id', id)
|
||||
if (!raw.length) throw new Error('No such client')
|
||||
|
||||
return cleanClientObject(raw[0])
|
||||
},
|
||||
// Update a client `id` in database with `data`
|
||||
updateClient: async function (id, data) {
|
||||
const fields = [
|
||||
'title', 'description', 'url', 'redirect_url', 'scope', 'verified'
|
||||
]
|
||||
|
||||
data = dataFilter(data, fields, ['scope', 'verified'])
|
||||
if (!data) throw new Error('Missing fields')
|
||||
|
||||
try {
|
||||
await Models.OAuth2Client.query().patchAndFetchById(id, data)
|
||||
await Models.OAuth2AuthorizedClient.query().delete().where('client_id', id)
|
||||
} catch (e) {
|
||||
throw new Error('No such client')
|
||||
}
|
||||
|
||||
return {}
|
||||
},
|
||||
// Create a new secret for a client
|
||||
newSecret: async function (id) {
|
||||
if (isNaN(id)) throw new Error('Invalid client ID')
|
||||
const secret = Users.Hash(16)
|
||||
|
||||
try {
|
||||
await Models.OAuth2Client.query().patchAndFetchById(id, { secret: secret })
|
||||
} catch (e) {
|
||||
throw new Error('No such client')
|
||||
}
|
||||
|
||||
return {}
|
||||
},
|
||||
// Create a new client
|
||||
createClient: async function (data, user) {
|
||||
const fields = [
|
||||
'title', 'description', 'url', 'redirect_url', 'scope'
|
||||
]
|
||||
|
||||
data = dataFilter(data, fields, ['scope'])
|
||||
if (!data) throw new Error('Missing fields')
|
||||
|
||||
const obj = Object.assign({
|
||||
secret: Users.Hash(16),
|
||||
grants: 'authorization_code',
|
||||
created_at: new Date(),
|
||||
user_id: user.id
|
||||
}, data)
|
||||
|
||||
return Models.OAuth2Client.query().insert(obj)
|
||||
},
|
||||
// Remove a client and all associated data
|
||||
removeClient: async function (id) {
|
||||
if (isNaN(id)) throw new Error('Invalid ID number')
|
||||
await Models.OAuth2Client.query().delete().where('id', id)
|
||||
await Models.OAuth2AuthorizedClient.query().delete().where('client_id', id)
|
||||
await Models.OAuth2AccessToken.query().delete().where('client_id', id)
|
||||
await Models.OAuth2RefreshToken.query().delete().where('client_id', id)
|
||||
return true
|
||||
},
|
||||
// List all bans (paginated)
|
||||
getAllBans: async function (page) {
|
||||
let count = await Models.Ban.query().count('id as ids')
|
||||
if (!count.length || !count[0].ids || isNaN(page)) {
|
||||
return { error: 'No bans on record' }
|
||||
}
|
||||
|
||||
count = count[0].ids
|
||||
const paginated = Users.Pagination(perPage, parseInt(count), page)
|
||||
const raw = await Models.Ban.query().offset(paginated.offset).limit(perPage)
|
||||
|
||||
const bans = []
|
||||
for (const i in raw) {
|
||||
const entry = raw[i]
|
||||
|
||||
bans.push(await cleanBanObject(entry))
|
||||
}
|
||||
|
||||
return {
|
||||
page: paginated, bans
|
||||
}
|
||||
},
|
||||
// Remove a ban
|
||||
removeBan: async function (banId) {
|
||||
return Models.Ban.query().delete().where('id', banId)
|
||||
},
|
||||
// Create a ban
|
||||
addBan: async function (data, adminId) {
|
||||
const user = await Users.User.get(parseInt(data.user_id))
|
||||
|
||||
if (!user) throw new Error('No such user.')
|
||||
if (user.id === adminId) throw new Error('Cannot ban yourself!')
|
||||
|
||||
const admin = await Users.User.get(adminId)
|
||||
|
||||
if (user.nw_privilege > admin.nw_privilege) throw new Error('Cannot ban user.')
|
||||
|
||||
const banAdd = {
|
||||
reason: data.reason || 'Unspecified ban',
|
||||
admin_id: adminId,
|
||||
user_id: user.id,
|
||||
expires_at: data.expires_at != null ? new Date(data.expires_at) : null,
|
||||
created_at: new Date(),
|
||||
associated_ip: data.ip_address || user.ip_address || null
|
||||
}
|
||||
|
||||
await Models.Ban.query().insert(banAdd)
|
||||
return {}
|
||||
},
|
||||
lockAccount: async function (userId) {
|
||||
const user = await Users.User.get(userId)
|
||||
if (user.id === 1 || user.nw_privilege > 2) {
|
||||
throw new Error('Cannot lock this user.')
|
||||
}
|
||||
|
||||
const lockId = Users.Hash(4)
|
||||
const userObf = {
|
||||
username: lockId,
|
||||
display_name: user.username,
|
||||
email: `${lockId}@icynet.eu`,
|
||||
password: null,
|
||||
activated: false,
|
||||
locked: true,
|
||||
avatar_file: null
|
||||
}
|
||||
|
||||
return Users.User.update(user, userObf)
|
||||
return {
|
||||
page: paginated,
|
||||
users: users
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = API
|
||||
export async function getUser (id) {
|
||||
const user = await User.get(id)
|
||||
if (!user) throw new Error('No such user')
|
||||
|
||||
return cleanUserObject(user, null)
|
||||
}
|
||||
|
||||
export async function editUser (id, data) {
|
||||
const user = await User.get(id)
|
||||
if (!user) throw new Error('No such user')
|
||||
|
||||
const fields = [
|
||||
'username', 'display_name', 'email', 'nw_privilege', 'activated'
|
||||
]
|
||||
|
||||
data = dataFilter(data, fields, ['nw_privilege', 'activated'])
|
||||
if (!data) throw new Error('Missing fields')
|
||||
|
||||
await User.update(user, data)
|
||||
|
||||
return {}
|
||||
}
|
||||
|
||||
export async function resendActivationEmail (id) {
|
||||
const user = await User.get(id)
|
||||
if (!user) throw new Error('No such user')
|
||||
|
||||
if (user.activated === 1) return {}
|
||||
|
||||
await Register.activationEmail(user)
|
||||
|
||||
return {}
|
||||
}
|
||||
|
||||
export async function revokeTotpToken (id) {
|
||||
const user = await User.get(id)
|
||||
if (!user) throw new Error('No such user')
|
||||
|
||||
await Models.TotpToken.query().delete().where('user_id', user.id)
|
||||
|
||||
return {}
|
||||
}
|
||||
|
||||
export async function sendPasswordEmail (id) {
|
||||
const user = await User.get(id)
|
||||
if (!user) throw new Error('No such user')
|
||||
|
||||
const token = await Reset.reset(user.email, false, true)
|
||||
|
||||
return { token }
|
||||
}
|
||||
|
||||
// Search for users by terms and fields
|
||||
export async function searchUsers (terms, fields = ['email']) {
|
||||
let qb = Models.User.query()
|
||||
|
||||
terms = terms.replace(/_/g, '\\_').replace(/%/g, '\\%')
|
||||
|
||||
qb = qb.where(fields[0], 'like', '%' + terms + '%')
|
||||
if (fields.length >= 1) {
|
||||
for (let i = 1; i < fields.length; i++) {
|
||||
qb = qb.orWhere(fields[i], 'like', '%' + terms + '%')
|
||||
}
|
||||
}
|
||||
|
||||
const rows = await qb.limit(8)
|
||||
if (!rows.length) return { error: 'No results' }
|
||||
|
||||
const cleaned = []
|
||||
for (const i in rows) {
|
||||
const userRaw = rows[i]
|
||||
cleaned.push(await cleanUserObject(userRaw, null))
|
||||
}
|
||||
|
||||
return cleaned
|
||||
}
|
||||
|
||||
// List all clients (paginated)
|
||||
export async function getAllClients (page) {
|
||||
let count = await Models.OAuth2Client.query().count('id as ids')
|
||||
if (!count.length || !count[0].ids || isNaN(page)) {
|
||||
return { error: 'No clients' }
|
||||
}
|
||||
|
||||
count = count[0].ids
|
||||
const paginated = Pagination(perPage, parseInt(count), page)
|
||||
const raw = await Models.OAuth2Client.query().offset(paginated.offset).limit(perPage)
|
||||
|
||||
const clients = []
|
||||
for (const i in raw) {
|
||||
const entry = raw[i]
|
||||
|
||||
clients.push(await cleanClientObject(entry))
|
||||
}
|
||||
|
||||
return {
|
||||
page: paginated, clients
|
||||
}
|
||||
}
|
||||
|
||||
// Get information about a client via id
|
||||
export async function getClient (id) {
|
||||
const raw = await Models.OAuth2Client.query().where('id', id)
|
||||
if (!raw.length) throw new Error('No such client')
|
||||
|
||||
return cleanClientObject(raw[0])
|
||||
}
|
||||
|
||||
// Update a client `id` in database with `data`
|
||||
export async function updateClient (id, data) {
|
||||
const fields = [
|
||||
'title', 'description', 'url', 'redirect_url', 'scope', 'verified'
|
||||
]
|
||||
|
||||
data = dataFilter(data, fields, ['scope', 'verified'])
|
||||
if (!data) throw new Error('Missing fields')
|
||||
|
||||
try {
|
||||
await Models.OAuth2Client.query().patchAndFetchById(id, data)
|
||||
await Models.OAuth2AuthorizedClient.query().delete().where('client_id', id)
|
||||
} catch (e) {
|
||||
throw new Error('No such client')
|
||||
}
|
||||
|
||||
return {}
|
||||
}
|
||||
|
||||
// Create a new secret for a client
|
||||
export async function newSecret (id) {
|
||||
if (isNaN(id)) throw new Error('Invalid client ID')
|
||||
const secret = Hash(16)
|
||||
|
||||
try {
|
||||
await Models.OAuth2Client.query().patchAndFetchById(id, { secret: secret })
|
||||
} catch (e) {
|
||||
throw new Error('No such client')
|
||||
}
|
||||
|
||||
return {}
|
||||
}
|
||||
|
||||
// Create a new client
|
||||
export async function createClient (data, user) {
|
||||
const fields = [
|
||||
'title', 'description', 'url', 'redirect_url', 'scope'
|
||||
]
|
||||
|
||||
data = dataFilter(data, fields, ['scope'])
|
||||
if (!data) throw new Error('Missing fields')
|
||||
|
||||
const obj = Object.assign({
|
||||
secret: Hash(16),
|
||||
grants: 'authorization_code',
|
||||
created_at: new Date(),
|
||||
user_id: user.id
|
||||
}, data)
|
||||
|
||||
return Models.OAuth2Client.query().insert(obj)
|
||||
}
|
||||
|
||||
// Remove a client and all associated data
|
||||
export async function removeClient (id) {
|
||||
if (isNaN(id)) throw new Error('Invalid ID number')
|
||||
await Models.OAuth2Client.query().delete().where('id', id)
|
||||
await Models.OAuth2AuthorizedClient.query().delete().where('client_id', id)
|
||||
await Models.OAuth2AccessToken.query().delete().where('client_id', id)
|
||||
await Models.OAuth2RefreshToken.query().delete().where('client_id', id)
|
||||
return true
|
||||
}
|
||||
|
||||
// List all bans (paginated)
|
||||
export async function getAllBans (page) {
|
||||
let count = await Models.Ban.query().count('id as ids')
|
||||
if (!count.length || !count[0].ids || isNaN(page)) {
|
||||
return { error: 'No bans on record' }
|
||||
}
|
||||
|
||||
count = count[0].ids
|
||||
const paginated = Pagination(perPage, parseInt(count), page)
|
||||
const raw = await Models.Ban.query().offset(paginated.offset).limit(perPage)
|
||||
|
||||
const bans = []
|
||||
for (const i in raw) {
|
||||
const entry = raw[i]
|
||||
|
||||
bans.push(await cleanBanObject(entry))
|
||||
}
|
||||
|
||||
return {
|
||||
page: paginated, bans
|
||||
}
|
||||
}
|
||||
|
||||
// Remove a ban
|
||||
export async function removeBan (banId) {
|
||||
return Models.Ban.query().delete().where('id', banId)
|
||||
}
|
||||
|
||||
// Create a ban
|
||||
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.id === adminId) throw new Error('Cannot ban yourself!')
|
||||
|
||||
const admin = await User.get(adminId)
|
||||
|
||||
if (user.nw_privilege > admin.nw_privilege) throw new Error('Cannot ban user.')
|
||||
|
||||
const banAdd = {
|
||||
reason: data.reason || 'Unspecified ban',
|
||||
admin_id: adminId,
|
||||
user_id: user.id,
|
||||
expires_at: data.expires_at != null ? new Date(data.expires_at) : null,
|
||||
created_at: new Date(),
|
||||
associated_ip: data.ip_address || user.ip_address || null
|
||||
}
|
||||
|
||||
await Models.Ban.query().insert(banAdd)
|
||||
return {}
|
||||
}
|
||||
|
||||
export async function lockAccount (userId) {
|
||||
const user = await User.get(userId)
|
||||
if (user.id === 1 || user.nw_privilege > 2) {
|
||||
throw new Error('Cannot lock this user.')
|
||||
}
|
||||
|
||||
const lockId = Hash(4)
|
||||
const userObf = {
|
||||
username: lockId,
|
||||
display_name: user.username,
|
||||
email: `${lockId}@icynet.eu`,
|
||||
password: null,
|
||||
activated: false,
|
||||
locked: true,
|
||||
avatar_file: null
|
||||
}
|
||||
|
||||
return User.update(user, userObf)
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ const email = new Email({
|
||||
})
|
||||
|
||||
// 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!')
|
||||
|
||||
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`
|
||||
async function pushMail (template, address, context) {
|
||||
export async function pushMail (template, address, context) {
|
||||
console.debug('Mail being sent: %s to %s', template, email)
|
||||
|
||||
return sendMail(address, template, context)
|
||||
}
|
||||
|
||||
// Transporter initialization
|
||||
async function init () {
|
||||
export async function init () {
|
||||
if (!config.email || config.email.enabled === false) return
|
||||
const transporter = nodemailer.createTransport(config.email.transport)
|
||||
|
||||
@ -53,8 +53,3 @@ async function init () {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
sendMail: sendMail,
|
||||
pushMail: pushMail,
|
||||
init: init
|
||||
}
|
||||
|
@ -4,429 +4,445 @@ import { v1 as uuidV1 } from 'uuid'
|
||||
import crypto from 'crypto'
|
||||
|
||||
import config from '../../scripts/load-config'
|
||||
import http from '../../scripts/http'
|
||||
import models from './models'
|
||||
import { httpGET } from '../../scripts/http'
|
||||
import * as models from './models'
|
||||
import Image from './image'
|
||||
import UAPI from './index'
|
||||
import { User, Hash } from './index'
|
||||
|
||||
const userFields = ['username', 'email', 'avatar_file', 'display_name', 'ip_address']
|
||||
|
||||
let twitterApp
|
||||
let discordApp
|
||||
|
||||
const API = {
|
||||
Common: {
|
||||
// Generate a hash based on the current session
|
||||
stateGenerator: (req) => {
|
||||
const sessionCrypto = req.session.id + ':' + config.server.session_secret
|
||||
return crypto.createHash('sha256').update(sessionCrypto).digest('hex')
|
||||
},
|
||||
// Find an user with an external ID
|
||||
getExternal: async (service, identifier) => {
|
||||
let extr = await models.External.query().where('service', service).andWhere('identifier', identifier)
|
||||
if (!extr || !extr.length) return null
|
||||
extr = extr[0]
|
||||
extr.user = null
|
||||
export class Common {
|
||||
// Generate a hash based on the current session
|
||||
static stateGenerator (req) {
|
||||
const sessionCrypto = req.session.id + ':' + config.server.session_secret
|
||||
return crypto.createHash('sha256').update(sessionCrypto).digest('hex')
|
||||
}
|
||||
|
||||
if (extr.user_id !== null) {
|
||||
const user = await UAPI.User.get(extr.user_id)
|
||||
if (user) {
|
||||
extr.user = user
|
||||
}
|
||||
}
|
||||
|
||||
return extr
|
||||
},
|
||||
// Get user ban status
|
||||
getBan: async (user, ipAddress) => {
|
||||
const banList = await UAPI.User.getBanStatus(ipAddress || user.id, ipAddress != null)
|
||||
return banList
|
||||
},
|
||||
// Create a new `external` instance for a user
|
||||
new: async (service, identifier, user) => {
|
||||
const data = {
|
||||
user_id: user.id,
|
||||
service: service,
|
||||
identifier: identifier,
|
||||
created_at: new Date()
|
||||
}
|
||||
|
||||
await await models.External.query().insert(data)
|
||||
return true
|
||||
},
|
||||
// Create a new user
|
||||
newUser: async (service, identifier, data) => {
|
||||
if (config.external.registrations !== true) throw new Error('Registrations from third-party websites are not allowed.')
|
||||
const udataLimited = Object.assign({
|
||||
activated: 1,
|
||||
created_at: new Date(),
|
||||
updated_at: new Date(),
|
||||
uuid: uuidV1()
|
||||
}, data)
|
||||
|
||||
// Some data cleanups
|
||||
|
||||
// Limit display name length
|
||||
udataLimited.display_name = udataLimited.display_name.substring(0, 32)
|
||||
|
||||
// Remove illegal characters from the username
|
||||
udataLimited.username = udataLimited.username.replace(/\W+/gi, '')
|
||||
|
||||
// Limit user name length
|
||||
udataLimited.username = udataLimited.username.substring(0, 26)
|
||||
|
||||
// Check if the username is already taken
|
||||
if (await UAPI.User.get(udataLimited.username) != null || udataLimited.username.length < 4) {
|
||||
udataLimited.username = udataLimited.username + UAPI.Hash(4)
|
||||
}
|
||||
|
||||
// Check if the email given to us is already registered, if so,
|
||||
// tell them to log in first.
|
||||
if (udataLimited.email && udataLimited.email !== '') {
|
||||
const getByEmail = await UAPI.User.get(udataLimited.email)
|
||||
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.')
|
||||
}
|
||||
}
|
||||
|
||||
// Create a new user based on the information we got from an external service
|
||||
const newUser = await models.User.query().insert(udataLimited)
|
||||
await API.Common.new(service, identifier, newUser)
|
||||
|
||||
return newUser
|
||||
},
|
||||
// Remove an `external` object (thus unlinking from a service)
|
||||
remove: async (user, service) => {
|
||||
user = await UAPI.User.ensureObject(user, ['password'])
|
||||
const userExterns = await models.External.query().orderBy('created_at', 'asc').where('user_id', user.id)
|
||||
|
||||
if (!userExterns.length) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Do not remove the service the user signed up with
|
||||
if (userExterns[0] && (user.password === '' || user.password === null) && userExterns[0].service === service) {
|
||||
return false
|
||||
}
|
||||
|
||||
return models.External.query().delete().where('user_id', user.id).andWhere('service', service)
|
||||
},
|
||||
// Common code for all auth callbacks
|
||||
callback: async (identifier, uid, user, ipAddress, remoteData, avatarFunc) => {
|
||||
const exists = await API.Common.getExternal(identifier, uid)
|
||||
// Find an user with an external ID
|
||||
static async getExternal (service, identifier) {
|
||||
let extr = await models.External.query().where('service', service).andWhere('identifier', identifier)
|
||||
if (!extr || !extr.length) return null
|
||||
extr = extr[0]
|
||||
extr.user = null
|
||||
|
||||
if (extr.user_id !== null) {
|
||||
const user = await User.get(extr.user_id)
|
||||
if (user) {
|
||||
// Get bans for user
|
||||
const bans = await API.Common.getBan(user)
|
||||
if (bans.length) return { banned: bans, ip: false }
|
||||
|
||||
if (exists) return { error: null, user: user }
|
||||
|
||||
await API.Common.new(identifier, uid, user)
|
||||
return { error: null, user: user }
|
||||
extr.user = user
|
||||
}
|
||||
|
||||
// Callback succeeded with user id and the external table exists, we log in the user
|
||||
if (exists) {
|
||||
// Get bans for user
|
||||
const bans = await API.Common.getBan(exists.user)
|
||||
|
||||
if (bans.length) return { banned: bans, ip: false }
|
||||
return { error: null, user: exists.user }
|
||||
}
|
||||
|
||||
// Get bans for IP address
|
||||
const bans = await API.Common.getBan(null, ipAddress)
|
||||
if (bans.length) return { banned: bans, ip: true }
|
||||
|
||||
// Run the function for avatar fetching
|
||||
let avatar = null
|
||||
if (avatarFunc) {
|
||||
avatar = await avatarFunc(remoteData)
|
||||
}
|
||||
|
||||
// Assign the data
|
||||
const newUData = Object.assign({
|
||||
email: remoteData.email || '',
|
||||
avatar_file: avatar,
|
||||
ip_address: ipAddress
|
||||
}, remoteData)
|
||||
|
||||
// Remove unnecessary fields
|
||||
for (const i in newUData) {
|
||||
if (userFields.indexOf(i) === -1) {
|
||||
delete newUData[i]
|
||||
}
|
||||
}
|
||||
|
||||
let newUser
|
||||
try {
|
||||
newUser = await API.Common.newUser(identifier, uid, newUData)
|
||||
} catch (e) {
|
||||
return { error: e.message }
|
||||
}
|
||||
|
||||
return { error: null, user: newUser }
|
||||
}
|
||||
},
|
||||
Facebook: {
|
||||
getAvatar: async (rawData) => {
|
||||
let profilepic = null
|
||||
|
||||
if (rawData.picture) {
|
||||
if (rawData.picture.is_silhouette === false && rawData.picture.url) {
|
||||
const imgdata = await Image.downloadImage(rawData.picture.url)
|
||||
if (imgdata && imgdata.fileName) {
|
||||
profilepic = imgdata.fileName
|
||||
}
|
||||
}
|
||||
}
|
||||
return extr
|
||||
}
|
||||
|
||||
return profilepic
|
||||
},
|
||||
callback: async (user, authResponse, ipAddress) => {
|
||||
if (!authResponse) {
|
||||
return { error: 'No Authorization' }
|
||||
}
|
||||
// Get user ban status
|
||||
static async getBan (user, ipAddress) {
|
||||
const banList = await User.getBanStatus(ipAddress || user.id, ipAddress != null)
|
||||
return banList
|
||||
}
|
||||
|
||||
const uid = authResponse.userID
|
||||
if (!uid) {
|
||||
return { error: 'No Authorization' }
|
||||
}
|
||||
|
||||
// Get facebook user information in order to create a new user or verify
|
||||
let fbdata
|
||||
const intel = {
|
||||
access_token: authResponse.accessToken,
|
||||
fields: 'name,email,picture,short_name'
|
||||
}
|
||||
|
||||
try {
|
||||
fbdata = await http.GET('https://graph.facebook.com/v2.10/' + uid + '?' + qs.stringify(intel))
|
||||
fbdata = JSON.parse(fbdata)
|
||||
} catch (e) {
|
||||
return { error: 'Could not get user information', errorObject: e }
|
||||
}
|
||||
|
||||
if (fbdata.error) {
|
||||
return { error: fbdata.error.message }
|
||||
}
|
||||
|
||||
const cleanedData = Object.assign(fbdata, {
|
||||
username: fbdata.short_name || 'FB' + UAPI.Hash(4),
|
||||
display_name: fbdata.name,
|
||||
email: fbdata.email || ''
|
||||
})
|
||||
|
||||
return API.Common.callback('facebook', uid, user, ipAddress, cleanedData, API.Facebook.getAvatar)
|
||||
// Create a new `external` instance for a user
|
||||
static async new (service, identifier, user) {
|
||||
const data = {
|
||||
user_id: user.id,
|
||||
service: service,
|
||||
identifier: identifier,
|
||||
created_at: new Date()
|
||||
}
|
||||
},
|
||||
Twitter: {
|
||||
getAvatar: async function (rawData) {
|
||||
let profilepic = null
|
||||
|
||||
if (rawData.profile_image_url_https) {
|
||||
const imgdata = await Image.downloadImage(rawData.profile_image_url_https)
|
||||
if (imgdata && imgdata.fileName) {
|
||||
profilepic = imgdata.fileName
|
||||
}
|
||||
}
|
||||
await models.External.query().insert(data)
|
||||
return true
|
||||
}
|
||||
|
||||
return profilepic
|
||||
},
|
||||
oauthApp: function () {
|
||||
if (!twitterApp) {
|
||||
const redirectUri = config.server.domain + '/api/external/twitter/callback'
|
||||
twitterApp = new oauth.PromiseOAuth(
|
||||
'https://api.twitter.com/oauth/request_token',
|
||||
'https://api.twitter.com/oauth/access_token',
|
||||
config.external.twitter.api,
|
||||
config.external.twitter.api_secret,
|
||||
'1.0A',
|
||||
redirectUri,
|
||||
'HMAC-SHA1'
|
||||
)
|
||||
}
|
||||
},
|
||||
getRequestToken: async function () {
|
||||
if (!twitterApp) API.Twitter.oauthApp()
|
||||
let tokens
|
||||
// Create a new user
|
||||
static async newUser (service, identifier, data) {
|
||||
if (config.external.registrations !== true) throw new Error('Registrations from third-party websites are not allowed.')
|
||||
const udataLimited = Object.assign({
|
||||
activated: 1,
|
||||
created_at: new Date(),
|
||||
updated_at: new Date(),
|
||||
uuid: uuidV1()
|
||||
}, data)
|
||||
|
||||
try {
|
||||
tokens = await twitterApp.getOAuthRequestToken()
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
return { error: 'No tokens returned' }
|
||||
}
|
||||
// Some data cleanups
|
||||
|
||||
if (tokens[2].oauth_callback_confirmed !== 'true') return { error: 'No tokens returned.' }
|
||||
// Limit display name length
|
||||
udataLimited.display_name = udataLimited.display_name.substring(0, 32)
|
||||
|
||||
return { error: null, token: tokens[0], token_secret: tokens[1] }
|
||||
},
|
||||
getAccessTokens: async function (token, secret, verifier) {
|
||||
if (!twitterApp) API.Twitter.oauthApp()
|
||||
let tokens
|
||||
// Remove illegal characters from the username
|
||||
udataLimited.username = udataLimited.username.replace(/\W+/gi, '')
|
||||
|
||||
try {
|
||||
tokens = await twitterApp.getOAuthAccessToken(token, secret, verifier)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
return { error: 'No tokens returned' }
|
||||
}
|
||||
// Limit user name length
|
||||
udataLimited.username = udataLimited.username.substring(0, 26)
|
||||
|
||||
if (!tokens || !tokens.length) return { error: 'No tokens returned' }
|
||||
|
||||
return { error: null, access_token: tokens[0], access_token_secret: tokens[1] }
|
||||
},
|
||||
callback: async function (user, accessTokens, ipAddress) {
|
||||
if (!twitterApp) API.Twitter.oauthApp()
|
||||
let twdata
|
||||
try {
|
||||
const resp = await twitterApp.get('https://api.twitter.com/1.1/account/verify_credentials.json?include_email=true',
|
||||
accessTokens.access_token, accessTokens.access_token_secret)
|
||||
twdata = JSON.parse(resp[0])
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
return { error: 'Failed to verify user credentials.' }
|
||||
}
|
||||
|
||||
const uid = twdata.id_str
|
||||
|
||||
const cleanedData = Object.assign(twdata, {
|
||||
username: twdata.screen_name,
|
||||
display_name: twdata.name,
|
||||
email: twdata.email || ''
|
||||
})
|
||||
|
||||
return API.Common.callback('twitter', uid, user, ipAddress, cleanedData, API.Twitter.getAvatar)
|
||||
// Check if the username is already taken
|
||||
if (await User.get(udataLimited.username) != null || udataLimited.username.length < 4) {
|
||||
udataLimited.username = udataLimited.username + Hash(4)
|
||||
}
|
||||
},
|
||||
Google: {
|
||||
getAvatar: async (rawData) => {
|
||||
let profilepic = null
|
||||
if (rawData.image) {
|
||||
const imgdata = await Image.downloadImage(rawData.image)
|
||||
if (imgdata && imgdata.fileName) {
|
||||
profilepic = imgdata.fileName
|
||||
}
|
||||
|
||||
// Check if the email given to us is already registered, if so,
|
||||
// tell them to log in first.
|
||||
if (udataLimited.email && udataLimited.email !== '') {
|
||||
const getByEmail = await User.get(udataLimited.email)
|
||||
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.')
|
||||
}
|
||||
|
||||
return profilepic
|
||||
},
|
||||
callback: async (user, data, ipAddress) => {
|
||||
let uid
|
||||
|
||||
try {
|
||||
const test = await http.GET('https://www.googleapis.com/oauth2/v3/tokeninfo?id_token=' + data.id_token)
|
||||
if (!test) throw new Error('No response!')
|
||||
|
||||
const jsondata = JSON.parse(test)
|
||||
if (!jsondata || !jsondata.email || !jsondata.name) throw new Error('Please allow Basic Profile and Email.')
|
||||
|
||||
if (jsondata.email !== data.email || jsondata.name !== data.name) throw new Error('Conflicting data. Please try again!')
|
||||
|
||||
if (new Date(parseInt(jsondata.exp) * 1000) < Date.now()) throw new Error('Expired token! Please try again!')
|
||||
|
||||
uid = jsondata.sub
|
||||
} catch (e) {
|
||||
return { error: e.message }
|
||||
}
|
||||
|
||||
const cleanedData = Object.assign(data, {
|
||||
username: data.name,
|
||||
display_name: data.name,
|
||||
email: data.email || ''
|
||||
})
|
||||
|
||||
return API.Common.callback('google', uid, user, ipAddress, cleanedData, API.Google.getAvatar)
|
||||
}
|
||||
},
|
||||
Discord: {
|
||||
getAvatar: async (rawData) => {
|
||||
let profilepic = null
|
||||
const aviSnowflake = rawData.avatar
|
||||
if (aviSnowflake) {
|
||||
try {
|
||||
const avpt = await Image.downloadImage('https://cdn.discordapp.com/avatars/' + rawData.id + '/' + aviSnowflake + '.png')
|
||||
if (avpt && avpt.fileName) {
|
||||
profilepic = avpt.fileName
|
||||
}
|
||||
} catch (e) {
|
||||
profilepic = null
|
||||
}
|
||||
}
|
||||
|
||||
return profilepic
|
||||
},
|
||||
oauth2App: function () {
|
||||
if (discordApp) return
|
||||
discordApp = new oauth.PromiseOAuth2(
|
||||
config.external.discord.api,
|
||||
config.external.discord.api_secret,
|
||||
'https://discordapp.com/api/',
|
||||
'oauth2/authorize',
|
||||
'oauth2/token'
|
||||
)
|
||||
// Create a new user based on the information we got from an external service
|
||||
const newUser = await models.User.query().insert(udataLimited)
|
||||
await Common.new(service, identifier, newUser)
|
||||
|
||||
discordApp.useAuthorizationHeaderforGET(true)
|
||||
},
|
||||
getAuthorizeURL: function (req) {
|
||||
if (!discordApp) API.Discord.oauth2App()
|
||||
const state = API.Common.stateGenerator(req)
|
||||
const redirectUri = config.server.domain + '/api/external/discord/callback'
|
||||
return newUser
|
||||
}
|
||||
|
||||
const params = {
|
||||
client_id: config.external.discord.api,
|
||||
redirect_uri: redirectUri,
|
||||
scope: 'identify email',
|
||||
response_type: 'code',
|
||||
state: state
|
||||
}
|
||||
// Remove an `external` object (thus unlinking from a service)
|
||||
static async remove (user, service) {
|
||||
user = await User.ensureObject(user, ['password'])
|
||||
const userExterns = await models.External.query().orderBy('created_at', 'asc').where('user_id', user.id)
|
||||
|
||||
const url = discordApp.getAuthorizeUrl(params)
|
||||
|
||||
return { error: null, state: state, url: url }
|
||||
},
|
||||
getAccessToken: async function (code) {
|
||||
if (!discordApp) API.Discord.oauth2App()
|
||||
|
||||
const redirectUri = config.server.domain + '/api/external/discord/callback'
|
||||
let tokens
|
||||
try {
|
||||
tokens = await discordApp.getOAuthAccessToken(code, { grant_type: 'authorization_code', redirect_uri: redirectUri })
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
return { error: 'No Authorization' }
|
||||
}
|
||||
|
||||
if (!tokens.length) return { error: 'No Tokens' }
|
||||
tokens = tokens[2]
|
||||
|
||||
return { error: null, accessToken: tokens.access_token }
|
||||
},
|
||||
callback: async function (user, accessToken, ipAddress) {
|
||||
if (!discordApp) API.Discord.oauth2App()
|
||||
|
||||
let ddata
|
||||
try {
|
||||
const resp = await discordApp.get('https://discordapp.com/api/users/@me', accessToken)
|
||||
ddata = JSON.parse(resp)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
return { error: 'Could not get user information' }
|
||||
}
|
||||
|
||||
const uid = ddata.id
|
||||
|
||||
// Create a new user
|
||||
const cleanedData = Object.assign(ddata, {
|
||||
display_name: ddata.username,
|
||||
email: ddata.email || ''
|
||||
})
|
||||
|
||||
return API.Common.callback('discord', uid, user, ipAddress, cleanedData, API.Discord.getAvatar)
|
||||
if (!userExterns.length) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Do not remove the service the user signed up with
|
||||
if (userExterns[0] && (user.password === '' || user.password === null) && userExterns[0].service === service) {
|
||||
return false
|
||||
}
|
||||
|
||||
return models.External.query().delete().where('user_id', user.id).andWhere('service', service)
|
||||
}
|
||||
|
||||
// Common code for all auth callbacks
|
||||
static async callback (identifier, uid, user, ipAddress, remoteData, avatarFunc) {
|
||||
const exists = await Common.getExternal(identifier, uid)
|
||||
|
||||
if (user) {
|
||||
// Get bans for user
|
||||
const bans = await Common.getBan(user)
|
||||
if (bans.length) return { banned: bans, ip: false }
|
||||
|
||||
if (exists) return { error: null, user: user }
|
||||
|
||||
await Common.new(identifier, uid, user)
|
||||
return { error: null, user: user }
|
||||
}
|
||||
|
||||
// Callback succeeded with user id and the external table exists, we log in the user
|
||||
if (exists) {
|
||||
// Get bans for user
|
||||
const bans = await Common.getBan(exists.user)
|
||||
|
||||
if (bans.length) return { banned: bans, ip: false }
|
||||
return { error: null, user: exists.user }
|
||||
}
|
||||
|
||||
// Get bans for IP address
|
||||
const bans = await Common.getBan(null, ipAddress)
|
||||
if (bans.length) return { banned: bans, ip: true }
|
||||
|
||||
// Run the function for avatar fetching
|
||||
let avatar = null
|
||||
if (avatarFunc) {
|
||||
avatar = await avatarFunc(remoteData)
|
||||
}
|
||||
|
||||
// Assign the data
|
||||
const newUData = Object.assign({
|
||||
email: remoteData.email || '',
|
||||
avatar_file: avatar,
|
||||
ip_address: ipAddress
|
||||
}, remoteData)
|
||||
|
||||
// Remove unnecessary fields
|
||||
for (const i in newUData) {
|
||||
if (userFields.indexOf(i) === -1) {
|
||||
delete newUData[i]
|
||||
}
|
||||
}
|
||||
|
||||
let newUser
|
||||
try {
|
||||
newUser = await Common.newUser(identifier, uid, newUData)
|
||||
} catch (e) {
|
||||
return { error: e.message }
|
||||
}
|
||||
|
||||
return { error: null, user: newUser }
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = API
|
||||
export class Facebook {
|
||||
static async getAvatar (rawData) {
|
||||
let profilepic = null
|
||||
|
||||
if (rawData.picture) {
|
||||
if (rawData.picture.is_silhouette === false && rawData.picture.url) {
|
||||
const imgdata = await Image.downloadImage(rawData.picture.url)
|
||||
if (imgdata && imgdata.fileName) {
|
||||
profilepic = imgdata.fileName
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return profilepic
|
||||
}
|
||||
|
||||
static async callback (user, authResponse, ipAddress) {
|
||||
if (!authResponse) {
|
||||
return { error: 'No Authorization' }
|
||||
}
|
||||
|
||||
const uid = authResponse.userID
|
||||
if (!uid) {
|
||||
return { error: 'No Authorization' }
|
||||
}
|
||||
|
||||
// Get facebook user information in order to create a new user or verify
|
||||
let fbdata
|
||||
const intel = {
|
||||
access_token: authResponse.accessToken,
|
||||
fields: 'name,email,picture,short_name'
|
||||
}
|
||||
|
||||
try {
|
||||
fbdata = await httpGET('https://graph.facebook.com/v2.10/' + uid + '?' + qs.stringify(intel))
|
||||
fbdata = JSON.parse(fbdata)
|
||||
} catch (e) {
|
||||
return { error: 'Could not get user information', errorObject: e }
|
||||
}
|
||||
|
||||
if (fbdata.error) {
|
||||
return { error: fbdata.error.message }
|
||||
}
|
||||
|
||||
const cleanedData = Object.assign(fbdata, {
|
||||
username: fbdata.short_name || 'FB' + Hash(4),
|
||||
display_name: fbdata.name,
|
||||
email: fbdata.email || ''
|
||||
})
|
||||
|
||||
return Common.callback('facebook', uid, user, ipAddress, cleanedData, Facebook.getAvatar)
|
||||
}
|
||||
}
|
||||
|
||||
export class Twitter {
|
||||
static async getAvatar (rawData) {
|
||||
let profilepic = null
|
||||
|
||||
if (rawData.profile_image_url_https) {
|
||||
const imgdata = await Image.downloadImage(rawData.profile_image_url_https)
|
||||
if (imgdata && imgdata.fileName) {
|
||||
profilepic = imgdata.fileName
|
||||