fix(oauth): déduire l'URI de redirection du domaine public réel

Sans APP_URL, Facebook et TikTok recevaient
http://localhost:5555/api/{facebook,tiktok}/callback : une URI que les
fournisseurs refusent, donc aucune page connectable derrière un reverse
proxy. Le repli localhost n'est plus utilisé que hors requête HTTP ; en
service, la base publique est reconstruite depuis X-Forwarded-Proto /
X-Forwarded-Host (trust proxy est déjà activé).

- `resolvePublicBaseUrl(req)` centralise la résolution (APP_URL prioritaire)
- l'URI de redirection est déduite de la requête au démarrage du flux, à
  l'échange du code et dans l'affichage des paramètres : les trois restent
  identiques, comme l'exige OAuth
- docker-compose ne force plus APP_URL à localhost, qui rendait l'auto
  détection inopérante
- cookie de session en `secure: 'auto'` à défaut d'APP_URL, pour ne pas
  émettre un cookie non sécurisé derrière un proxy HTTPS

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Db2EazEcvnqgTTeg8oGTif
This commit is contained in:
Claude committed 2026-09-11 18:01:28 +00:00
1 parent 94e961d0f1
commit 3af7f9418b
9 files changed
+119 -27

No files matched your search

+4 -4
View File
@@ -50,7 +50,7 @@ facebookRouter.get('/config', async (req: Request, res: Response) => {
configured,
appId: config?.appId || (process.env.FACEBOOK_APP_ID ? "(défini par variable d'environnement)" : ''),
hasAppSecret: !!(config?.appSecret || process.env.FACEBOOK_APP_SECRET),
redirectUri: facebookOAuthService.getRedirectUri(),
redirectUri: facebookOAuthService.getRedirectUri(req),
scopes: facebookOAuthService.getScopes().split(','),
});
} catch (error) {
@@ -76,7 +76,7 @@ facebookRouter.put('/config', async (req: Request, res: Response) => {
res.json({
success: true,
appId: config.appId,
redirectUri: facebookOAuthService.getRedirectUri(),
redirectUri: facebookOAuthService.getRedirectUri(req),
});
} catch (error: any) {
console.error('❌ [Facebook] Erreur enregistrement configuration:', error);
@@ -102,7 +102,7 @@ facebookRouter.get('/connect', async (req: Request, res: Response) => {
const state = crypto.randomBytes(16).toString('hex');
(req.session as any).facebookOAuthState = state;
const authUrl = await facebookOAuthService.buildAuthorizationUrl(state);
const authUrl = await facebookOAuthService.buildAuthorizationUrl(state, req);
// La session doit être écrite avant la redirection, sinon l'état est perdu
req.session.save((err) => {
@@ -145,7 +145,7 @@ facebookRouter.get('/callback', async (req: Request, res: Response) => {
return res.redirect(buildReturnUrl('error', "Aucun code d'autorisation reçu de Facebook."));
}
const userToken = await facebookOAuthService.exchangeCodeForUserToken(code);
const userToken = await facebookOAuthService.exchangeCodeForUserToken(code, req);
const targets = await facebookOAuthService.listPages(userToken.accessToken);
if (targets.length === 0) {
+4 -4
View File
@@ -47,7 +47,7 @@ tiktokRouter.get('/config', async (req: Request, res: Response) => {
configured,
clientKey: config?.clientKey || (process.env.TIKTOK_CLIENT_KEY ? '(défini par variable d\'environnement)' : ''),
hasClientSecret: !!(config?.clientSecret || process.env.TIKTOK_CLIENT_SECRET),
redirectUri: tiktokService.getRedirectUri(),
redirectUri: tiktokService.getRedirectUri(req),
});
} catch (error) {
console.error('❌ [TikTok] Erreur lecture configuration:', error);
@@ -72,7 +72,7 @@ tiktokRouter.put('/config', async (req: Request, res: Response) => {
res.json({
success: true,
clientKey: config.clientKey,
redirectUri: tiktokService.getRedirectUri(),
redirectUri: tiktokService.getRedirectUri(req),
});
} catch (error: any) {
console.error('❌ [TikTok] Erreur enregistrement configuration:', error);
@@ -98,7 +98,7 @@ tiktokRouter.get('/connect', async (req: Request, res: Response) => {
const state = crypto.randomBytes(16).toString('hex');
(req.session as any).tiktokOAuthState = state;
const authUrl = await tiktokService.buildAuthorizationUrl(state);
const authUrl = await tiktokService.buildAuthorizationUrl(state, req);
// La session doit être écrite avant la redirection, sinon l'état est perdu
req.session.save((err) => {
@@ -141,7 +141,7 @@ tiktokRouter.get('/callback', async (req: Request, res: Response) => {
return res.redirect(buildReturnUrl('error', "Aucun code d'autorisation reçu de TikTok."));
}
const tokens = await tiktokService.exchangeCodeForToken(code);
const tokens = await tiktokService.exchangeCodeForToken(code, req);
const profile = await tiktokService.getUserInfo(tokens.access_token);
const openId = profile.openId || tokens.open_id;