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

+13 -7
View File
@@ -15,7 +15,9 @@
* compte). Le code s'adapte automatiquement à ce que le compte autorise.
*/
import type { Request } from 'express';
import { storage } from '../storage';
import { resolvePublicBaseUrl } from '../utils/public_url';
import type { SocialPage } from '@shared/schema';
const AUTH_BASE_URL = 'https://www.tiktok.com/v2/auth/authorize/';
@@ -98,26 +100,30 @@ export class TiktokService {
}
}
getRedirectUri(): string {
/**
* URI de redirection OAuth. `req` sert de repli quand `APP_URL` n'est pas
* défini : le domaine public est alors lu sur la requête plutôt que remplacé
* par `http://localhost:5555`.
*/
getRedirectUri(req?: Request): string {
if (process.env.TIKTOK_REDIRECT_URI) {
return process.env.TIKTOK_REDIRECT_URI;
}
const base = (process.env.APP_URL || 'http://localhost:5555').replace(/\/$/, '');
return `${base}/api/tiktok/callback`;
return `${resolvePublicBaseUrl(req)}/api/tiktok/callback`;
}
/**
* URL d'autorisation à ouvrir pour connecter le compte TikTok d'un magasin.
* Chaque magasin répète ce flux : un compte = une ligne dans social_pages.
*/
async buildAuthorizationUrl(state: string): Promise<string> {
async buildAuthorizationUrl(state: string, req?: Request): Promise<string> {
const { clientKey } = await this.getCredentials();
const params = new URLSearchParams({
client_key: clientKey,
scope: process.env.TIKTOK_SCOPES || DEFAULT_SCOPES,
response_type: 'code',
redirect_uri: this.getRedirectUri(),
redirect_uri: this.getRedirectUri(req),
state,
});
@@ -125,7 +131,7 @@ export class TiktokService {
}
/** Échange le code d'autorisation contre un couple access/refresh token. */
async exchangeCodeForToken(code: string): Promise<TiktokTokenResponse> {
async exchangeCodeForToken(code: string, req?: Request): Promise<TiktokTokenResponse> {
const { clientKey, clientSecret } = await this.getCredentials();
return await this.requestToken({
@@ -134,7 +140,7 @@ export class TiktokService {
// TikTok renvoie le code URL-encodé dans la query string
code: decodeURIComponent(code),
grant_type: 'authorization_code',
redirect_uri: this.getRedirectUri(),
redirect_uri: this.getRedirectUri(req),
});
}