123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265 |
- import cookieParser from 'cookie-parser';
- import express from 'express';
- import fetch from 'node-fetch';
- import morgan from 'morgan';
- import Sentry from '@sentry/node';
- import bancho from './bancho.js';
- import db from './database.js';
- import {sync_discord_info} from './discord_updates.js';
- import {get_or_create_user, get_user} from './user.js';
- import Config from './util/config.js';
- import {render_error, gen_url} from './util/helpers.js';
- import {register_routes as register_api_routes} from './website_api.js';
- import {register_routes as register_static_routes} from './website_ssr.js';
- const auth_page = gen_url(0, '/auth');
- async function listen() {
- const app = express();
- if (Config.ENABLE_SENTRY) {
- app.use(Sentry.Handlers.requestHandler());
- }
- app.use(morgan(':status :method :url :response-time ms (:remote-addr)'));
- app.enable('trust proxy');
- app.set('trust proxy', () => true);
- app.use(cookieParser(Config.cookie_secret));
- await register_api_routes(app);
- app.get('/', async (req, http_res) => {
- http_res.redirect('/lobbies/');
- });
- // Convenience redirect so we only have to generate the oauth URL here.
- app.get('/osu_login', (req, http_res) => {
- http_res.redirect(`https://osu.ppy.sh/oauth/authorize?client_id=${Config.osu_v2api_client_id}&response_type=code&state=login&scope=identify&redirect_uri=${auth_page}`);
- });
- // Convenience URL that redirects you to your profile or the login page
- app.get('/me/', (req, http_res) => {
- if (req.signedCookies.user) {
- return http_res.redirect(`${req.protocol}://${req.hostname}/u/${req.signedCookies.user}`);
- } else {
- http_res.cookie('login_to', `${req.protocol}://${req.hostname}/me/`, {
- domain: Config.domain_name,
- maxAge: 30000,
- httpOnly: false,
- secure: Config.IS_PRODUCTION,
- signed: false,
- sameSite: 'Strict',
- });
- return http_res.redirect('/osu_login');
- }
- });
- app.get('/auth_migrate', (req, http_res) => {
- try {
- const info = db.prepare(`SELECT osu_id FROM token WHERE token = ?`).get(req.query.token);
- http_res.cookie('user', info.osu_id, {
- domain: Config.domain_name,
- maxAge: 34560000000,
- httpOnly: false,
- secure: Config.IS_PRODUCTION,
- signed: true,
- sameSite: 'Strict',
- });
- if (req.query.last_match_id) {
- http_res.cookie('last_match', req.query.last_match_id, {
- domain: Config.domain_name,
- maxAge: 34560000000,
- httpOnly: false,
- secure: Config.IS_PRODUCTION,
- signed: true,
- sameSite: 'Strict',
- });
- }
- } catch {
- // If token was invalid, just ignore
- }
- // Nginx will serve index.html on 404
- // Frontend will redirect to the correct page using the login_to cookie
- return http_res.status(404).end();
- });
- app.get('/auth', async (req, http_res) => {
- let res;
- if (!req.query.code) {
- return http_res.status(403).end(render_error('No auth code provided.'));
- }
- const fetchOauthTokens = async (req) => {
- // Get oauth tokens from osu!api
- try {
- res = await fetch('https://osu.ppy.sh/oauth/token', {
- method: 'post',
- body: JSON.stringify({
- client_id: Config.osu_v2api_client_id,
- client_secret: Config.osu_v2api_client_secret,
- code: req.query.code,
- grant_type: 'authorization_code',
- redirect_uri: auth_page,
- }),
- headers: {
- 'Accept': 'application/json',
- 'Content-Type': 'application/json',
- },
- });
- } catch (err) {
- console.error(res.status, await res.text());
- http_res.status(503).end(render_error('Internal server error, try again later.'));
- return null;
- }
- if (!res.ok) {
- console.error(res.status, await res.text());
- http_res.status(403).end(render_error('Invalid auth code.'));
- return null;
- }
- // Get osu user id from the received oauth tokens
- return await res.json();
- };
- const fetchUserProfile = async (req, access_token) => {
- try {
- res = await fetch('https://osu.ppy.sh/api/v2/me/osu', {
- method: 'get',
- headers: {
- 'Accept': 'application/json',
- 'Content-Type': 'application/json',
- 'Authorization': `Bearer ${access_token}`,
- },
- });
- } catch (err) {
- http_res.status(503).send(render_error('Internal server error, try again later.'));
- console.error(res.status, await res.text());
- http_res.end();
- return null;
- }
- if (!res.ok) {
- http_res.status(503).send(render_error('osu!web sent us bogus tokens. Sorry, idk what to do now'));
- http_res.end();
- return null;
- }
- return await res.json();
- };
- if (req.query.state === 'login') {
- const tokens = await fetchOauthTokens(req);
- if (tokens === null) return;
- const user_profile = await fetchUserProfile(req, tokens.access_token);
- if (user_profile === null) return;
- // Initialize the user in the database if needed
- try {
- await get_or_create_user(user_profile.id);
- } catch (err) {
- return http_res.end(render_error(`Couldn't fetch profile... Are you banned?`));
- }
- http_res.cookie('user', user_profile.id, {
- domain: Config.domain_name,
- maxAge: 34560000000,
- httpOnly: false,
- secure: Config.IS_PRODUCTION,
- signed: true,
- sameSite: 'Strict',
- });
- return http_res.end(render_error('Logged in successfully!'));
- } else {
- // Get discord user id from ephemeral token
- const ephemeral_token = req.query.state;
- const discord_user_id = db.prepare(`SELECT discord_id FROM token WHERE token = ?`).get(ephemeral_token);
- if (!discord_user_id) {
- return http_res.status(403).end(render_error('Invalid Discord token. Please click the "Link account" button once again.'));
- }
- const tokens = await fetchOauthTokens(req);
- if (tokens === null) return;
- const user_profile = await fetchUserProfile(req, tokens.access_token);
- if (user_profile == null) return;
- // Initialize the user in the database if needed
- let user;
- try {
- user = await get_or_create_user(user_profile.id);
- } catch (err) {
- return http_res.end(render_error(`Couldn't fetch profile... Are you banned?`));
- }
- // Link accounts! Finally.
- db.prepare(`UPDATE user SET discord_user_id = ? WHERE user_id = ?`).run(discord_user_id.discord_id, user_profile.id);
- db.prepare(`DELETE FROM token WHERE token = ?`).run(ephemeral_token);
- user.discord_user_id = discord_user_id.discord_id;
- await sync_discord_info(user, 'Account link');
- return http_res.end(render_error('Account has been linked!'));
- }
- });
- app.post('/get-invite/:banchoId', async (req, http_res) => {
- if (!req.signedCookies.user) {
- http_res.status(403).json({error: 'You need to be authenticated to get an invite.'});
- return;
- }
- let inviting_lobby = null;
- for (const lobby of bancho.joined_lobbies) {
- if (lobby.invite_id == req.params.banchoId) {
- inviting_lobby = lobby;
- break;
- }
- }
- if (!inviting_lobby) {
- http_res.status(404).json({error: 'Could not find the lobby. Maybe it has been closed?'});
- return;
- }
- try {
- const user = await get_user(req.signedCookies.user);
- await bancho.privmsg(user.username, `${user.username}, here's your invite: [http://osump://${inviting_lobby.invite_id}/ ${inviting_lobby.name}]`);
- http_res.status(200).json({success: true});
- } catch (err) {
- http_res.status(503).json({error: 'Failed to send invite', details: err.message});
- }
- });
- // Legacy URLs
- app.get('/leaderboard/:ruleset/*', (req, res, next) => {
- if (req.params.ruleset.indexOf('page-') != -1) {
- return next();
- }
- const new_path = req.path.replace(`/${req.params.ruleset}/`, '/');
- return res.redirect(`${req.protocol}://${req.params.ruleset}.${Config.domain_name}${new_path}`);
- });
- app.get('/u/:userId/:ruleset/*', (req, res, next) => {
- if (req.params.ruleset.indexOf('page-') != -1) {
- return next();
- }
- const new_path = req.path.replace(`/${req.params.ruleset}/`, '/');
- return res.redirect(`${req.protocol}://${req.params.ruleset}.${Config.domain_name}${new_path}`);
- });
- await register_static_routes(app);
- if (Config.ENABLE_SENTRY) {
- app.use(Sentry.Handlers.errorHandler());
- }
- app.listen(3001, () => {
- console.log(`Listening on :${3001}. Access via ${gen_url(0, '/')}`);
- });
- }
- export {listen};
|