overhauled mock auth solution
This commit is contained in:
@@ -15,6 +15,7 @@ import { logger } from '../services/logging/logger';
|
||||
const querystring = require('querystring');
|
||||
import { performance } from 'perf_hooks';
|
||||
import { CacheService } from '../services/cache/cache';
|
||||
import { Strategy as CustomStrategy } from 'passport-custom';
|
||||
|
||||
|
||||
function parseJwt(token) {
|
||||
@@ -33,125 +34,137 @@ const devLogin = (req: any, res: any, next: any) => {
|
||||
});
|
||||
};
|
||||
|
||||
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) {
|
||||
if (process.env.AUTH_MODE === "mock") {
|
||||
passport.use('mock', new CustomStrategy(async (req, done) => {
|
||||
const mockUser = { memberId: 1 };
|
||||
return done(null, mockUser);
|
||||
}))
|
||||
} else {
|
||||
passport.use('oidc', 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);
|
||||
let con;
|
||||
// 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);
|
||||
let con;
|
||||
|
||||
try {
|
||||
con = await pool.getConnection();
|
||||
try {
|
||||
con = await pool.getConnection();
|
||||
|
||||
await con.beginTransaction();
|
||||
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) {
|
||||
//login
|
||||
memberId = existing[0].id;
|
||||
logger.info('auth', `Existing member login`, {
|
||||
memberId,
|
||||
issuer,
|
||||
});
|
||||
|
||||
} 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
|
||||
const result = await con.query(
|
||||
`UPDATE members SET authentik_sub = ?, authentik_issuer = ? WHERE id = ?;`,
|
||||
[sub, issuer, memberId]
|
||||
)
|
||||
logger.info('auth', `Existing member claimed via Discord`, {
|
||||
//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) {
|
||||
//login
|
||||
memberId = existing[0].id;
|
||||
logger.info('auth', `Existing member login`, {
|
||||
memberId,
|
||||
discordID,
|
||||
issuer,
|
||||
});
|
||||
|
||||
} else {
|
||||
// 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);
|
||||
//otherwise: create account mode
|
||||
const jwt = parseJwt(jwtClaims);
|
||||
const discordID = jwt.discord?.id as number;
|
||||
|
||||
logger.info('auth', `New member account created`, {
|
||||
memberId,
|
||||
username,
|
||||
issuer,
|
||||
});
|
||||
//check if account is available to claim
|
||||
if (discordID)
|
||||
memberId = await mapDiscordtoID(discordID);
|
||||
|
||||
if (discordID && memberId) {
|
||||
// claim account
|
||||
const result = await con.query(
|
||||
`UPDATE members SET authentik_sub = ?, authentik_issuer = ? WHERE id = ?;`,
|
||||
[sub, issuer, memberId]
|
||||
)
|
||||
logger.info('auth', `Existing member claimed via Discord`, {
|
||||
memberId,
|
||||
discordID,
|
||||
issuer,
|
||||
});
|
||||
|
||||
} else {
|
||||
// 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);
|
||||
|
||||
logger.info('auth', `New member account created`, {
|
||||
memberId,
|
||||
username,
|
||||
issuer,
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await con.query(`UPDATE members SET last_login = ? WHERE id = ?`, [toDateTime(new Date()), memberId])
|
||||
await con.query(`UPDATE members SET last_login = ? WHERE id = ?`, [toDateTime(new Date()), memberId])
|
||||
|
||||
await con.commit();
|
||||
return cb(null, { memberId });
|
||||
} catch (error) {
|
||||
logger.error('auth', `Authentication transaction failed`, {
|
||||
issuer,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
});
|
||||
await con.commit();
|
||||
return cb(null, { memberId });
|
||||
} catch (error) {
|
||||
logger.error('auth', `Authentication transaction failed`, {
|
||||
issuer,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
});
|
||||
|
||||
if (con) {
|
||||
try {
|
||||
await con.rollback();
|
||||
} catch (rollbackError) {
|
||||
logger.error('auth', `Rollback failed`, {
|
||||
error: rollbackError instanceof Error
|
||||
? rollbackError.message
|
||||
: String(rollbackError),
|
||||
});
|
||||
if (con) {
|
||||
try {
|
||||
await con.rollback();
|
||||
} catch (rollbackError) {
|
||||
logger.error('auth', `Rollback failed`, {
|
||||
error: rollbackError instanceof Error
|
||||
? rollbackError.message
|
||||
: String(rollbackError),
|
||||
});
|
||||
}
|
||||
}
|
||||
return cb(error);
|
||||
} finally {
|
||||
if (con) con.release();
|
||||
}
|
||||
return cb(error);
|
||||
} finally {
|
||||
if (con) con.release();
|
||||
}
|
||||
}));
|
||||
}));
|
||||
}
|
||||
|
||||
router.get('/login', (req, res, next) => {
|
||||
// Store redirect target in session if provided
|
||||
req.session.redirectTo = req.query.redirect;
|
||||
req.session.redirectTo = req.query.redirect as string;
|
||||
|
||||
if (process.env.AUTH_MODE === 'mock') {
|
||||
return devLogin(req, res, next);
|
||||
}
|
||||
|
||||
next();
|
||||
}, passport.authenticate('openidconnect'));
|
||||
const strategy = process.env.AUTH_MODE === 'mock' ? 'mock' : 'oidc';
|
||||
|
||||
passport.authenticate(strategy, {
|
||||
successRedirect: (req.session.redirectTo || process.env.CLIENT_URL),
|
||||
failureRedirect: '/login'
|
||||
})(req, res, next);
|
||||
});
|
||||
|
||||
router.get('/callback', (req, res, next) => {
|
||||
|
||||
//escape if mocked
|
||||
if (process.env.AUTH_MODE === 'mock') {
|
||||
return res.redirect(process.env.CLIENT_URL || '/');
|
||||
}
|
||||
|
||||
const redirectURI = req.session.redirectTo;
|
||||
passport.authenticate('openidconnect', (err, user) => {
|
||||
passport.authenticate('oidc', (err, user) => {
|
||||
if (err) return next(err);
|
||||
if (!user) return res.redirect(process.env.CLIENT_URL);
|
||||
|
||||
@@ -181,12 +194,21 @@ router.get('/logout', [requireLogin], function (req, res, next) {
|
||||
sameSite: 'lax'
|
||||
});
|
||||
|
||||
if (process.env.AUTH_MODE === 'mock') {
|
||||
return res.redirect(process.env.CLIENT_URL || '/');
|
||||
}
|
||||
|
||||
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));
|
||||
const endSessionUri = process.env.AUTH_END_SESSION_URI;
|
||||
if (endSessionUri) {
|
||||
return res.redirect(endSessionUri + '?' + querystring.stringify(params));
|
||||
} else {
|
||||
return res.redirect(process.env.CLIENT_URL || '/');
|
||||
}
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user