import express from 'express' import bcrypt from 'bcrypt' import { PromiseOAuth2 } from 'oauth-libre' import crypto from 'crypto' import { dbPromise } from './database' const router = express.Router() async function userInfoPublic (id) { const db = await dbPromise const 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 (id) { const db = await dbPromise 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 (oauth, registrations) { router.get('/info', userMiddleware, async (req, res) => { res.jsonp(await userInfoPublic(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(parseInt(req.params.id))) }) if (!oauth) return router const oauth2 = new PromiseOAuth2(oauth.clientId, oauth.clientSecret, oauth.baseUrl, oauth.authorizePath, oauth.tokenPath) router.get('/login/oauth/_redirect', async (req, res) => { const code = req.query.code const 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!') } const 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.. const db = await dbPromise const 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 await db.run('INSERT INTO User (username,email,image,created) VALUES (?,?,?,?)', userInfo.username, userInfo.email, userInfo.image, new Date()) const newU = await db.get('SELECT * FROM User WHERE username = ?', userInfo.username) if (!newU) throw new Error('Something went wrong!') 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) => { const state = crypto.randomBytes(16).toString('hex') req.session.oauthState = state 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('/') const header = req.get('authorization') || '' const token = header.split(/\s+/).pop() || '' const auth = Buffer.from(token, 'base64').toString() const parts = auth.split(/:/) const username = parts[0] const password = parts[1] const 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') } const db = await dbPromise const 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 const 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 }