129 lines
4.2 KiB
JavaScript
129 lines
4.2 KiB
JavaScript
const express = require('express');
|
|
const router = express.Router();
|
|
const dotenv = require('dotenv');
|
|
|
|
dotenv.config();
|
|
|
|
/**
|
|
* Página simples de login
|
|
*/
|
|
router.get('/login', (req, res) => {
|
|
// Redirect straight to Microsoft OAuth start to avoid an extra click/page
|
|
return res.redirect('/auth/microsoft');
|
|
});
|
|
|
|
/**
|
|
* Inicia login Microsoft
|
|
*/
|
|
router.get('/auth/microsoft', (req, res) => {
|
|
const params = new URLSearchParams({
|
|
client_id: process.env.OAUTH_CLIENT_ID,
|
|
response_type: 'code',
|
|
redirect_uri: process.env.OAUTH_REDIRECT_URI,
|
|
response_mode: 'query',
|
|
scope: 'openid profile email',
|
|
});
|
|
|
|
res.redirect(
|
|
`https://login.microsoftonline.com/${process.env.OAUTH_TENANT_ID}/oauth2/v2.0/authorize?${params}`
|
|
);
|
|
});
|
|
|
|
/**
|
|
* Callback do Azure
|
|
*/
|
|
// shared handler so we accept both /auth/microsoft/callback and /auth/callback
|
|
async function oauthCallbackHandler(req, res) {
|
|
const code = req.query.code;
|
|
// Verbose logging for debugging the OAuth callback flow
|
|
console.log('[OAuth callback] incoming query:', {
|
|
code: req.query.code ? '[present]' : '[absent]',
|
|
state: req.query.state,
|
|
error: req.query.error,
|
|
error_description: req.query.error_description ? '[present]' : undefined,
|
|
});
|
|
|
|
if (!code) {
|
|
console.warn('[OAuth callback] no authorization code present, redirecting to /login');
|
|
return res.redirect('/login');
|
|
}
|
|
|
|
try {
|
|
console.log('[OAuth callback] exchanging code for tokens (will not log secrets)');
|
|
|
|
const tokenRespRaw = await fetch(
|
|
`https://login.microsoftonline.com/${process.env.OAUTH_TENANT_ID}/oauth2/v2.0/token`,
|
|
{
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
body: new URLSearchParams({
|
|
client_id: process.env.OAUTH_CLIENT_ID,
|
|
client_secret: process.env.OAUTH_CLIENT_SECRET,
|
|
grant_type: 'authorization_code',
|
|
code,
|
|
redirect_uri: process.env.OAUTH_REDIRECT_URI,
|
|
}),
|
|
}
|
|
);
|
|
|
|
console.log('[OAuth callback] token endpoint HTTP status:', tokenRespRaw.status);
|
|
const tokenResp = await tokenRespRaw.json();
|
|
|
|
// Log token response keys but never print tokens or client secret
|
|
console.log('[OAuth callback] token response keys:', Object.keys(tokenResp));
|
|
|
|
if (tokenResp.error) {
|
|
console.error('[OAuth callback] token endpoint returned error:', tokenResp.error, tokenResp.error_description || '');
|
|
return res.redirect('/login');
|
|
}
|
|
|
|
if (!tokenResp.id_token) {
|
|
console.warn('[OAuth callback] No id_token received — token response:', Object.keys(tokenResp));
|
|
}
|
|
|
|
// decodificar id_token (JWT) com cuidado — não logar o token inteiro
|
|
let payload = {};
|
|
try {
|
|
const parts = (tokenResp.id_token || '').split('.');
|
|
if (parts.length >= 2) {
|
|
payload = JSON.parse(Buffer.from(parts[1], 'base64').toString());
|
|
} else {
|
|
console.warn('[OAuth callback] id_token malformed');
|
|
}
|
|
} catch (e) {
|
|
console.error('[OAuth callback] failed to decode id_token payload:', e && (e.message || e));
|
|
}
|
|
|
|
// Log a few safe user-identifying fields
|
|
console.log('[OAuth callback] id_token payload (safe fields):', {
|
|
name: payload.name,
|
|
preferred_username: payload.preferred_username || payload.upn || payload.email,
|
|
oid: payload.oid,
|
|
});
|
|
|
|
// salva sessão
|
|
req.session.user = {
|
|
authenticated: true,
|
|
email: payload.preferred_username,
|
|
name: payload.name,
|
|
oid: payload.oid,
|
|
};
|
|
|
|
console.log('[OAuth callback] session created for user:', req.session.user && { email: req.session.user.email, name: req.session.user.name });
|
|
res.redirect('/');
|
|
} catch (err) {
|
|
console.error('[OAuth callback] unexpected error during token exchange or session creation:', err && (err.stack || err.message || err));
|
|
res.redirect('/login');
|
|
}
|
|
}
|
|
|
|
router.get('/auth/microsoft/callback', oauthCallbackHandler);
|
|
// some Azure app registrations (or tooling like ngrok) may use /auth/callback — accept that too
|
|
router.get('/auth/callback', oauthCallbackHandler);
|
|
|
|
router.get('/logout', (req, res) => {
|
|
req.session.destroy(() => res.redirect('/login'));
|
|
});
|
|
|
|
module.exports = router;
|