btrtracks/src/user.js

160 lines
5.0 KiB
JavaScript

import express from 'express'
import bcrypt from 'bcrypt'
import { PromiseOAuth2 } from 'oauth-libre'
import crypto from 'crypto'
const router = express.Router()
async function userInfoPublic (db, id) {
let u = await db.get('SELECT id, username, image FROM User WHERE id = ?', id)
if (!u) return {}
return {
id: u.id,
username: u.username,
image: u.image
}
}
export async function userInfo (db, id) {
return db.get('SELECT * FROM User WHERE id = ?', id)
}
export function userMiddleware (req, res, next) {
if (!req.session || !req.session.user) return res.status(401).jsonp({ error: 'Unauthorized' })
next()
}
export function user (dbPromise, oauth, registrations) {
router.get('/info', userMiddleware, async (req, res) => {
res.jsonp(await userInfoPublic(await dbPromise, req.session.user))
})
router.get('/info/:id', userMiddleware, async (req, res) => {
if (isNaN(parseInt(req.params.id))) throw new Error('Invalid user ID!')
res.jsonp(await userInfoPublic(await dbPromise, parseInt(req.params.id)))
})
if (!oauth) return router
let oauth2 = new PromiseOAuth2(oauth.clientId, oauth.clientSecret, oauth.baseUrl, oauth.authorizePath, oauth.tokenPath)
router.get('/login/oauth/_redirect', async (req, res) => {
let code = req.query.code
let state = req.query.state
if (!code || !state) throw new Error('Something went wrong!')
if (!req.session || !req.session.oauthState || req.session.oauthState !== state) throw new Error('Possible request forgery detected! Try again.')
delete req.session.oauthState
let tokens
try {
tokens = await oauth2.getOAuthAccessToken(code, { grant_type: 'authorization_code', redirect_uri: oauth.redirectUri })
} catch (e) {
throw new Error('No authorization!')
}
let accessToken = tokens[2].access_token
// Get user information on remote
let userInfo
try {
userInfo = await oauth2.get(oauth.baseUrl + oauth.userPath, accessToken)
userInfo = JSON.parse(userInfo)
} catch (e) {
userInfo = null
}
if (!userInfo) throw new Error('Couldn\'t get user information!')
// Let's see if there's a link for this user already..
let db = await dbPromise
let userLocal = await db.get('SELECT * FROM OAuth WHERE remoteId = ?', userInfo.id)
// User and link both exist
if (userLocal) {
if (req.session.user) throw new Error('You are already logged in!')
req.session.user = userLocal.userId
return res.redirect('/')
}
// If we're logged in, create a link
if (req.session.user) {
await db.run('INSERT INTO OAuth (userId,remoteId,created) VALUES (?,?,?)', req.session.user, userInfo.id, new Date())
return res.redirect('/')
}
if (!registrations) throw new Error('Registrations are currently closed!')
// Create a new user and log in
let newU = await db.get('INSERT INTO User (username,email,image,created) VALUES (?,?,?,?)', userInfo.username, userInfo.email, userInfo.image, new Date())
await db.run('INSERT INTO OAuth (userId,remoteId,created) VALUES (?,?,?)', newU.id, userInfo.id, new Date())
req.session.user = newU.id
res.redirect('/')
})
router.get('/login/oauth', async (req, res) => {
let state
if (req.session && req.session.oauthState) {
state = req.session.oauthState
} else {
req.session.oauthState = crypto.randomBytes(16).toString('hex')
}
return res.redirect(oauth2.getAuthorizeUrl({
'redirect_uri': oauth.redirectUri,
'scope': oauth.scope,
'response_type': 'code',
'state': state
}))
})
router.use('/login', async (req, res, next) => {
if (req.session && req.session.user) return res.redirect('/')
let header = req.get('authorization') || ''
let token = header.split(/\s+/).pop() || ''
let auth = Buffer.from(token, 'base64').toString()
let parts = auth.split(/:/)
let username = parts[0]
let password = parts[1]
let message = oauth != null ? 'Enter \'oauth\' to log in remotely.' : 'Log in'
req.message = message
if ((!username || !password) && (username !== 'oauth' && oauth)) {
return next()
}
if ((username === 'oauth' || username === 'oa') && oauth) {
return res.redirect('/user/login/oauth')
}
let db = await dbPromise
let user = await db.get('SELECT * FROM User WHERE username = ?', username)
if (!user) return next()
if (!user.password && oauth) {
return res.redirect('/user/login/oauth')
}
// Compare passwords
let ures = await bcrypt.compare(password, user.password)
if (!ures) return next()
// Set login success
req.message = ''
req.session.user = user.id
res.redirect('/')
}, function (req, res, next) {
if (!req.message) return next()
res.status(401).set('WWW-Authenticate', 'Basic realm="' + req.message + '", charset="UTF-8"').end()
})
router.use('/logout', function (req, res) {
delete req.session.user
res.redirect('/')
})
return router
}