144 lines
5.1 KiB
TypeScript
144 lines
5.1 KiB
TypeScript
import type { Client } from 'openid-client';
|
|
import { generators, TokenSet } from 'openid-client';
|
|
import { Request, Response } from 'express';
|
|
|
|
export function setupAuthRoutes(
|
|
app: any,
|
|
client: Client,
|
|
redirectUri: string,
|
|
scope: string,
|
|
cookieOptionsBase: any
|
|
) {
|
|
// Login: genera PKCE e reindirizza a Keycloak
|
|
app.get('/auth/login', (req: Request, res: Response) => {
|
|
const code_verifier = generators.codeVerifier();
|
|
const code_challenge = generators.codeChallenge(code_verifier);
|
|
|
|
res.cookie('pkce_verifier', code_verifier, {
|
|
...cookieOptionsBase,
|
|
maxAge: 10 * 60 * 1000, // 10 minuti
|
|
});
|
|
|
|
const authUrl = client.authorizationUrl({
|
|
scope,
|
|
code_challenge,
|
|
code_challenge_method: 'S256',
|
|
redirect_uri: redirectUri,
|
|
});
|
|
|
|
res.redirect(authUrl);
|
|
});
|
|
|
|
// Callback: scambia il code per tokenSet e salva access+id+refresh
|
|
app.get('/auth/callback', async (req: Request, res: Response) => {
|
|
try {
|
|
const params = client.callbackParams(req);
|
|
const verifier = req.signedCookies['pkce_verifier'];
|
|
const tokenSet = await client.callback(redirectUri, params, { code_verifier: verifier });
|
|
|
|
res.clearCookie('pkce_verifier');
|
|
|
|
// Salva tutti i token necessari come cookie firmati e httpOnly
|
|
res.cookie('access_token', tokenSet.access_token, { ...cookieOptionsBase });
|
|
res.cookie('id_token', tokenSet.id_token, { ...cookieOptionsBase });
|
|
|
|
// Importante: salva anche il refresh_token per il rinnovo automatico
|
|
if (tokenSet.refresh_token) {
|
|
res.cookie('refresh_token', tokenSet.refresh_token, {
|
|
...cookieOptionsBase,
|
|
// opzionale: persistenza, deve essere <= refresh token lifetime configurato in Keycloak
|
|
maxAge: 7 * 24 * 60 * 60 * 1000, // 7 giorni
|
|
});
|
|
}
|
|
|
|
res.redirect('/');
|
|
} catch (err) {
|
|
const refresh = req.signedCookies['refresh_token'];
|
|
|
|
if (refresh) {
|
|
await client.revoke(refresh, 'refresh_token');
|
|
}
|
|
|
|
res.clearCookie('access_token', { ...cookieOptionsBase });
|
|
res.clearCookie('id_token', { ...cookieOptionsBase });
|
|
res.clearCookie('refresh_token', { ...cookieOptionsBase });
|
|
console.error('[OIDC] Errore callback:', err);
|
|
res.status(500).json({ error: 'login_failed' });
|
|
}
|
|
});
|
|
|
|
// Userinfo: usa SEMPRE l'access token, facendo refresh se scaduto
|
|
app.get('/api/userinfo', async (req: Request, res: Response) => {
|
|
try {
|
|
const access = req.signedCookies['access_token'];
|
|
const refresh = req.signedCookies['refresh_token'];
|
|
|
|
// Se non ho proprio token, è 401
|
|
if (!access && !refresh) {
|
|
return res.status(401).json({ error: 'unauthorized' });
|
|
}
|
|
|
|
// Provo userinfo con l'access token corrente
|
|
try {
|
|
const userinfo = await client.userinfo(access);
|
|
return res.json(userinfo);
|
|
} catch (err) {
|
|
// Se fallisce, può essere scaduto: provo il refresh se disponibile
|
|
if (!refresh) {
|
|
return res.status(401).json({ error: 'invalid_or_expired_token' });
|
|
}
|
|
try {
|
|
const refreshed = await client.refresh(refresh);
|
|
|
|
// Aggiorna i cookie con i nuovi token
|
|
if (refreshed.access_token) {
|
|
res.cookie('access_token', refreshed.access_token, { ...cookieOptionsBase });
|
|
}
|
|
if (refreshed.id_token) {
|
|
res.cookie('id_token', refreshed.id_token, { ...cookieOptionsBase });
|
|
}
|
|
if (refreshed.refresh_token) {
|
|
// alcuni IdP ruotano il refresh_token: sempre sovrascrivere se presente
|
|
res.cookie('refresh_token', refreshed.refresh_token, {
|
|
...cookieOptionsBase,
|
|
maxAge: 7 * 24 * 60 * 60 * 1000,
|
|
});
|
|
}
|
|
|
|
const userinfo = await client.userinfo(refreshed.access_token);
|
|
return res.json(userinfo);
|
|
} catch (refreshErr) {
|
|
console.error('[OIDC] Errore nel refresh:', refreshErr);
|
|
// pulizia e 401 → sessione non più valida
|
|
res.clearCookie('access_token', { ...cookieOptionsBase });
|
|
res.clearCookie('id_token', { ...cookieOptionsBase });
|
|
res.clearCookie('refresh_token', { ...cookieOptionsBase });
|
|
//res.clearCookie('access_token');
|
|
//res.clearCookie('id_token');
|
|
//res.clearCookie('refresh_token');
|
|
return res.status(401).json({ error: 'refresh_failed' });
|
|
}
|
|
}
|
|
} catch (outerErr) {
|
|
console.error('[OIDC] Errore userinfo:', outerErr);
|
|
return res.status(401).json({ error: 'invalid_token' });
|
|
}
|
|
});
|
|
|
|
// Logout: pulisce tutti i cookie
|
|
app.post('/auth/logout', async (req: Request, res: Response) => {
|
|
const refresh = req.signedCookies['refresh_token'];
|
|
|
|
if (refresh) {
|
|
await client.revoke(refresh, 'refresh_token');
|
|
}
|
|
|
|
res.clearCookie('access_token', { ...cookieOptionsBase });
|
|
res.clearCookie('id_token', { ...cookieOptionsBase });
|
|
res.clearCookie('refresh_token', { ...cookieOptionsBase });
|
|
//res.clearCookie('access_token');
|
|
//res.clearCookie('id_token');
|
|
//res.clearCookie('refresh_token');
|
|
res.json({ ok: true });
|
|
});
|
|
}
|