This repository has been archived on 2022-11-26. You can view files and clone it, but cannot push or open issues or pull requests.
IcyNet.eu/server/api/external.js

433 lines
13 KiB
JavaScript
Raw Normal View History

import qs from 'querystring'
import oauth from 'oauth-libre'
import uuidV1 from 'uuid/v1'
import crypto from 'crypto'
import config from '../../scripts/load-config'
import http from '../../scripts/http'
import models from './models'
2017-11-30 21:45:21 +00:00
import Image from './image'
import UAPI from './index'
const userFields = ['username', 'email', 'avatar_file', 'display_name', 'ip_address']
2017-09-09 11:15:11 +00:00
let twitterApp
let discordApp
const API = {
Common: {
// Generate a hash based on the current session
stateGenerator: (req) => {
let 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
if (extr.user_id !== null) {
let user = await UAPI.User.get(extr.user_id)
if (user) {
extr.user = user
}
}
return extr
},
// Get user ban status
2017-08-27 12:41:44 +00:00
getBan: async (user, ipAddress) => {
let 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) => {
let 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
2017-09-10 09:42:12 +00:00
newUser: async (service, identifier, data) => {
2019-08-08 12:33:58 +00:00
if (config.external.registrations !== true) throw new Error('Registrations from third-party websites are not allowed.')
2017-09-10 09:42:12 +00:00
let udataLimited = Object.assign({
activated: 1,
created_at: new Date(),
updated_at: new Date(),
uuid: uuidV1()
2017-09-10 09:42:12 +00:00
}, data)
2017-11-23 16:26:53 +00:00
// 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)
2017-09-10 09:42:12 +00:00
// Check if the username is already taken
if (await UAPI.User.get(udataLimited.username) != null || udataLimited.username.length < 4) {
2017-09-10 09:42:12 +00:00
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.
2017-09-10 09:42:12 +00:00
if (udataLimited.email && udataLimited.email !== '') {
let 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.')
2017-09-10 09:42:12 +00:00
}
}
2017-11-23 16:26:53 +00:00
// Create a new user based on the information we got from an external service
2017-09-10 09:42:12 +00:00
let 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'])
let userExterns = await models.External.query().orderBy('created_at', 'asc').where('user_id', user.id)
if (!userExterns.length) {
return false
}
// Do not remove the service the user signed up with
if (userExterns[0] && (user.password === '' || user.password === null) && userExterns[0].service === service) {
return false
}
return models.External.query().delete().where('user_id', user.id).andWhere('service', service)
2017-08-25 16:42:30 +00:00
},
// Common code for all auth callbacks
callback: async (identifier, uid, user, ipAddress, remoteData, avatarFunc) => {
let exists = await API.Common.getExternal(identifier, uid)
2017-08-25 16:42:30 +00:00
if (user) {
// Get bans for user
let bans = await API.Common.getBan(user)
if (bans.length) return { banned: bans, ip: false }
2017-08-25 16:42:30 +00:00
if (exists) return {error: null, user: user}
await API.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
let bans = await API.Common.getBan(exists.user)
if (bans.length) return { banned: bans, ip: false }
return {error: null, user: exists.user}
2017-08-25 16:42:30 +00:00
}
// Get bans for IP address
let 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
let newUData = Object.assign({
email: remoteData.email || '',
avatar_file: avatar,
ip_address: ipAddress
}, remoteData)
// Remove unnecessary fields
for (let 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) {
2017-11-30 21:45:21 +00:00
let imgdata = await Image.downloadImage(rawData.picture.url)
if (imgdata && imgdata.fileName) {
profilepic = imgdata.fileName
}
}
}
return profilepic
},
callback: async (user, authResponse, ipAddress) => {
if (!authResponse) {
return {error: 'No Authorization'}
}
let uid = authResponse.userID
if (!uid) {
return {error: 'No Authorization'}
}
// Get facebook user information in order to create a new user or verify
let fbdata
let 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}
}
let 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)
}
},
Twitter: {
getAvatar: async function (rawData) {
let profilepic = null
if (rawData.profile_image_url_https) {
2017-11-30 21:45:21 +00:00
let imgdata = await Image.downloadImage(rawData.profile_image_url_https)
if (imgdata && imgdata.fileName) {
profilepic = imgdata.fileName
}
}
return profilepic
},
oauthApp: function () {
if (!twitterApp) {
let 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',
2019-08-08 12:33:58 +00:00
config.external.twitter.api,
config.external.twitter.api_secret,
'1.0A',
redirectUri,
'HMAC-SHA1'
)
}
},
getRequestToken: async function () {
if (!twitterApp) API.Twitter.oauthApp()
let tokens
2017-08-24 16:23:03 +00:00
try {
tokens = await twitterApp.getOAuthRequestToken()
} catch (e) {
console.error(e)
return {error: 'No tokens returned'}
}
2017-08-24 16:23:03 +00:00
if (tokens[2].oauth_callback_confirmed !== 'true') return {error: 'No tokens returned.'}
return {error: null, token: tokens[0], token_secret: tokens[1]}
},
getAccessTokens: async function (token, secret, verifier) {
if (!twitterApp) API.Twitter.oauthApp()
let tokens
try {
tokens = await twitterApp.getOAuthAccessToken(token, secret, verifier)
} catch (e) {
console.error(e)
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]}
},
callback: async function (user, accessTokens, ipAddress) {
if (!twitterApp) API.Twitter.oauthApp()
let twdata
try {
2017-08-24 16:23:03 +00:00
let 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.'}
}
let uid = twdata.id_str
let cleanedData = Object.assign(twdata, {
username: twdata.screen_name,
display_name: twdata.name,
email: twdata.email || ''
})
2017-08-27 12:41:44 +00:00
return API.Common.callback('twitter', uid, user, ipAddress, cleanedData, API.Twitter.getAvatar)
}
},
Google: {
getAvatar: async (rawData) => {
2017-08-25 16:42:30 +00:00
let profilepic = null
if (rawData.image) {
2017-11-30 21:45:21 +00:00
let imgdata = await Image.downloadImage(rawData.image)
2017-08-25 16:42:30 +00:00
if (imgdata && imgdata.fileName) {
profilepic = imgdata.fileName
}
}
return profilepic
},
2017-10-13 16:18:17 +00:00
callback: async (user, data, ipAddress) => {
let uid
try {
let test = await http.GET('https://www.googleapis.com/oauth2/v3/tokeninfo?id_token=' + data.id_token)
if (!test) throw new Error('No response!')
let 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}
}
let cleanedData = Object.assign(data, {
2017-11-23 16:26:53 +00:00
username: data.name,
2017-10-13 16:18:17 +00:00
display_name: data.name,
email: data.email || ''
})
2017-10-13 16:18:17 +00:00
return API.Common.callback('google', uid, user, ipAddress, cleanedData, API.Google.getAvatar)
2017-10-13 16:18:17 +00:00
}
},
Discord: {
getAvatar: async (rawData) => {
let profilepic = null
let aviSnowflake = rawData.avatar
if (aviSnowflake) {
try {
2017-11-30 21:45:21 +00:00
let 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
},
2017-08-24 16:23:03 +00:00
oauth2App: function () {
if (discordApp) return
discordApp = new oauth.PromiseOAuth2(
2019-08-08 12:33:58 +00:00
config.external.discord.api,
config.external.discord.api_secret,
'https://discordapp.com/api/',
'oauth2/authorize',
'oauth2/token'
)
discordApp.useAuthorizationHeaderforGET(true)
},
getAuthorizeURL: function (req) {
if (!discordApp) API.Discord.oauth2App()
let state = API.Common.stateGenerator(req)
let redirectUri = config.server.domain + '/api/external/discord/callback'
const params = {
2019-08-08 12:33:58 +00:00
'client_id': config.external.discord.api,
'redirect_uri': redirectUri,
'scope': 'identify email',
'response_type': 'code',
'state': state
}
let url = discordApp.getAuthorizeUrl(params)
return {error: null, state: state, url: url}
},
getAccessToken: async function (code) {
if (!discordApp) API.Discord.oauth2App()
let 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 {
let 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'}
}
let uid = ddata.id
// Create a new user
let cleanedData = Object.assign(ddata, {
display_name: ddata.username,
email: ddata.email || ''
})
2017-09-09 11:15:11 +00:00
return API.Common.callback('discord', uid, user, ipAddress, cleanedData, API.Discord.getAvatar)
}
}
}
module.exports = API