const passport = require('passport'); const OpenIDConnectStrategy = require('passport-openidconnect'); const dotenv = require('dotenv'); dotenv.config(); const express = require('express'); const { param } = require('./applications'); const router = express.Router(); import { Role } from '@app/shared/types/roles'; import pool from '../db'; import { requireLogin } from '../middleware/auth'; import { getUserRoles } from '../services/rolesService'; import { getUserState, mapDiscordtoID } from '../services/memberService'; import { MemberState } from '@app/shared/types/member'; import { toDateTime } from '@app/shared/utils/time'; const querystring = require('querystring'); function parseJwt(token) { return JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString()); } passport.use(new OpenIDConnectStrategy({ issuer: process.env.AUTH_ISSUER, authorizationURL: process.env.AUTH_DOMAIN + '/authorize/', tokenURL: process.env.AUTH_DOMAIN + '/token/', userInfoURL: process.env.AUTH_DOMAIN + '/userinfo/', clientID: process.env.AUTH_CLIENT_ID, clientSecret: process.env.AUTH_CLIENT_SECRET, callbackURL: process.env.AUTH_REDIRECT_URI, scope: ['openid', 'profile', 'discord'] }, async function verify(issuer, sub, profile, jwtClaims, accessToken, refreshToken, params, cb) { // console.log('--- OIDC verify() called ---'); // console.log('issuer:', issuer); // console.log('sub:', sub); // // console.log('discord:', discord); // console.log('profile:', profile); // console.log('jwt: ', parseJwt(jwtClaims)); // console.log('params:', params); try { var con = await pool.getConnection(); await con.beginTransaction(); //lookup existing user const existing = await con.query(`SELECT id FROM members WHERE authentik_issuer = ? AND authentik_sub = ? LIMIT 1;`, [issuer, sub]); let memberId: number | null = null; //if member exists if (existing.length > 0) { memberId = existing[0].id; } else { //otherwise: create account mode const jwt = parseJwt(jwtClaims); const discordID = jwt.discord?.id as number; //check if account is available to claim if (discordID) memberId = await mapDiscordtoID(discordID); if (discordID && memberId) { // claim account console.log("Claiming account"); const result = await con.query( `UPDATE members SET authentik_sub = ?, authentik_issuer = ? WHERE id = ?;`, [sub, issuer, memberId] ) } else { console.log("New Account"); // new account const username = sub.username; const result = await con.query( `INSERT INTO members (name, authentik_sub, authentik_issuer) VALUES (?, ?, ?)`, [username, sub, issuer] ) memberId = Number(result.insertId); } } await con.query(`UPDATE members SET last_login = ? WHERE id = ?`, [toDateTime(new Date()), memberId]) await con.commit(); return cb(null, { memberId }); } catch (error) { await con.rollback(); return cb(error); } finally { con.release(); } })); router.get('/login', (req, res, next) => { // Store redirect target in session if provided req.session.redirectTo = req.query.redirect; next(); }, passport.authenticate('openidconnect')); router.get('/callback', (req, res, next) => { const redirectURI = req.session.redirectTo; passport.authenticate('openidconnect', (err, user) => { if (err) return next(err); if (!user) return res.redirect(process.env.CLIENT_URL); req.logIn(user, err => { if (err) return next(err); // Use redirect saved from session const redirectTo = redirectURI || process.env.CLIENT_URL; delete req.session.redirectTo; return res.redirect(redirectTo); }); })(req, res, next); }); router.get('/logout', [requireLogin], function (req, res, next) { req.logout(function (err) { if (err) { return next(err); } req.session.destroy((err) => { if (err) { return next(err); } res.clearCookie('connect.sid', { path: '/', domain: process.env.CLIENT_DOMAIN, httpOnly: true, sameSite: 'lax' }); var params = { client_id: process.env.AUTH_CLIENT_ID, returnTo: process.env.CLIENT_URL }; res.redirect(process.env.AUTH_END_SESSION_URI + '?' + querystring.stringify(params)); }) }); }); passport.serializeUser(function (user, cb) { process.nextTick(function () { cb(null, user); }); }); passport.deserializeUser(function (user, cb) { process.nextTick(async function () { const memberID = user.memberId as number; var userData: { id: number, name: string, roles: Role[], state: MemberState }; try { var con = await pool.getConnection(); let userResults = await con.query(`SELECT id, name FROM members WHERE id = ?;`, [memberID]) userData = userResults[0]; let userRoles = await getUserRoles(memberID); userData.roles = userRoles || []; userData.state = await getUserState(memberID); } catch (error) { console.error(error) } finally { con.release(); } return cb(null, userData); }); }); declare global { namespace Express { interface Request { user: { id: number; name: string; roles: Role[]; state: MemberState; }; } } } export const authRouter = router;