Possibilité d'avoir une clé API Globale par famille

This commit is contained in:
2026-07-20 18:47:58 +02:00
parent 9eb19efd92
commit 441da1975a
12 changed files with 224 additions and 62 deletions
+1 -1
View File
@@ -247,7 +247,7 @@ export default function Aide() {
<td style={{ padding: '8px 10px 8px 0', verticalAlign: 'top', whiteSpace: 'nowrap' }}>
<code style={{ background: 'var(--surface-2)', padding: '1px 5px', borderRadius: 4 }}>crowdlending_get_investisseur</code>
</td>
<td style={{ padding: '8px 0', verticalAlign: 'top' }}>Profil de l'investisseur lié à la clé API (nom, type famille/entreprise, régime fiscal).</td>
<td style={{ padding: '8px 0', verticalAlign: 'top' }}>Profil de l'investisseur lié à la clé API (nom, type famille/entreprise, régime fiscal). Avec une clé « Famille et entreprises », renvoie la liste des profils du foyer.</td>
</tr>
<tr style={{ borderBottom: '1px solid var(--border)' }}>
<td style={{ padding: '8px 10px 8px 0', verticalAlign: 'top', whiteSpace: 'nowrap' }}>
+109 -27
View File
@@ -805,6 +805,12 @@ function NewApiKeyModal({ open, onClose, onCreated, investisseurs }) {
const [busy, setBusy] = useState(false);
const [err, setErr] = useState(null);
// La clé "Famille et entreprises" (scope_all) n'est autorisée que depuis le
// profil principal — cohérent avec l'enforcement côté serveur (routes/apiKeys.js).
// Si aucun principal n'est trouvé (ne devrait pas arriver), on masque
// simplement l'option plutôt que de proposer un choix qui échouera au submit.
const principal = investisseurs.find(i => i.is_principal);
useEffect(() => {
if (open) {
setNom('');
@@ -813,6 +819,8 @@ function NewApiKeyModal({ open, onClose, onCreated, investisseurs }) {
}
}, [open, investisseurs]);
const isScopeAll = investisseurId === 'all';
const submit = async (e) => {
e.preventDefault();
if (!nom.trim()) return setErr('Le nom de la clé est requis');
@@ -820,7 +828,10 @@ function NewApiKeyModal({ open, onClose, onCreated, investisseurs }) {
setBusy(true);
setErr(null);
try {
const created = await api.post('/api-keys', { nom: nom.trim(), investisseur_id: Number(investisseurId) });
const payload = isScopeAll
? { nom: nom.trim(), investisseur_id: principal.id, scope_all: true }
: { nom: nom.trim(), investisseur_id: Number(investisseurId) };
const created = await api.post('/api-keys', payload);
onCreated(created);
} catch (e) { setErr(e.message); }
finally { setBusy(false); }
@@ -841,10 +852,13 @@ function NewApiKeyModal({ open, onClose, onCreated, investisseurs }) {
{investisseurs.map(i => (
<option key={i.id} value={i.id}>{memberLabel(i)}</option>
))}
{principal && <option value="all">Famille et entreprises</option>}
</select>
</div>
<p className="text-muted" style={{ margin: 0, fontSize: 'var(--fs-sm)' }}>
La clé donne un accès en lecture seule aux données de cet investisseur. Elle ne sera affichée en clair qu'une seule fois.
{isScopeAll
? "La clé donnera un accès en lecture seule agrégé à tous les membres du foyer (comme la vue « Famille et entreprises » de l'app). Elle ne sera affichée en clair qu'une seule fois."
: "La clé donne un accès en lecture seule aux données de cet investisseur. Elle ne sera affichée en clair qu'une seule fois."}
</p>
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: 8, marginTop: 4 }}>
<button type="button" className="ghost" onClick={onClose}>Annuler</button>
@@ -1043,7 +1057,7 @@ function ApiKeysSection() {
{keys.map(k => (
<tr key={k.id}>
<td>{k.nom}</td>
<td>{k.investisseur_nom}</td>
<td>{k.scope_all ? 'Famille et entreprises' : k.investisseur_nom}</td>
<td style={{ fontFamily: 'monospace', fontSize: 12 }}>{k.key_prefix}</td>
<td>{fmtKeyDate(k.created_at)}</td>
<td>{fmtKeyDate(k.last_used_at)}</td>
@@ -1149,10 +1163,12 @@ function CopyBlock({ text }) {
}
function McpServerSection({ goToApiKeys }) {
const navigate = useNavigate();
const [mcpUrl, setMcpUrl] = useState(guessMcpUrl());
const [apiKey, setApiKey] = useState('');
const [manualLabel, setManualLabel] = useState(null); // null = auto-détecté depuis mcpUrl, sinon override manuel
const [debugFlag, setDebugFlag] = useState(false); // ajoute --debug : génère un fichier mcp-server-<nom>.log dédié (voir Dépannage)
const [systemCaFlag, setSystemCaFlag] = useState(true); // ajoute NODE_OPTIONS=--use-system-ca : contourne un antivirus/proxy qui intercepte le HTTPS (voir Dépannage) — coché par défaut, ne s'applique qu'en prod (voir envBlock)
const detectedLabel = detectLabelFromUrl(mcpUrl);
const label = manualLabel ?? detectedLabel;
@@ -1177,11 +1193,20 @@ function McpServerSection({ goToApiKeys }) {
// macOS/Linux, qui n'ont pas ce problème (npx s'exécute nativement).
const isWindows = typeof navigator !== 'undefined' && /win/i.test(navigator.platform || navigator.userAgent || '');
const envBlock = {
[envVarName]: apiKey || '<VOTRE_CLE_API>',
// Uniquement pertinent pour une URL distante (prod) : force Node à utiliser
// le magasin de certificats du système plutôt que le sien, pour accepter
// les connexions re-signées par un antivirus/proxy à inspection HTTPS
// (Avast, Kaspersky, proxy d'entreprise…) — voir Dépannage.
...(!isLocal && systemCaFlag ? { NODE_OPTIONS: '--use-system-ca' } : {}),
};
const configJson = JSON.stringify({
mcpServers: {
[serverKey]: isWindows
? { command: 'cmd', args: ['/c', 'npx', ...mcpRemoteArgs], env: { [envVarName]: apiKey || '<VOTRE_CLE_API>' } }
: { command: 'npx', args: mcpRemoteArgs, env: { [envVarName]: apiKey || '<VOTRE_CLE_API>' } },
? { command: 'cmd', args: ['/c', 'npx', ...mcpRemoteArgs], env: envBlock }
: { command: 'npx', args: mcpRemoteArgs, env: envBlock },
},
}, null, 2);
@@ -1261,12 +1286,15 @@ function McpServerSection({ goToApiKeys }) {
action nécessaire au-delà de renseigner l'URL et votre clé API.
</p>
)}
<p style={{ margin: '0 0 20px', fontSize: 'var(--fs-sm)', color: 'var(--text-muted)' }}>
Claude Desktop peut se connecter à plusieurs serveurs MCP en même temps : pour avoir dev et
prod accessibles simultanément, répétez ces étapes une deuxième fois avec l'autre URL (et une clé
API distincte) la clé de config (<code>{serverKey}</code> ci-dessous) s'ajuste automatiquement,
Claude Desktop ne confondra jamais les deux portefeuilles.
</p>
{isLocal && (
<p style={{ margin: '0 0 20px', fontSize: 'var(--fs-sm)', color: 'var(--text-muted)' }}>
Claude Desktop peut se connecter à plusieurs serveurs MCP en même temps : pour avoir aussi votre
portefeuille en production accessible depuis Claude Desktop, répétez ces étapes une deuxième fois
avec l'URL de production (et une clé API distincte) la clé de config
(<code>{serverKey}</code> ci-dessous) s'ajuste automatiquement, Claude Desktop ne confondra jamais
les deux connexions.
</p>
)}
<h4 style={{ margin: '0 0 6px', fontSize: 'var(--fs-sm)' }}>3. Copier la configuration</h4>
<p style={{ margin: '0 0 10px', fontSize: 'var(--fs-sm)', color: 'var(--text-muted)' }}>
@@ -1295,10 +1323,27 @@ function McpServerSection({ goToApiKeys }) {
style={{ width: '100%', maxWidth: 520, borderRadius: 8, border: '1px solid var(--border)', display: 'block', margin: '0 auto 16px' }}
onError={(e) => { e.currentTarget.style.display = 'none'; }}
/>
<label style={{ display: 'flex', alignItems: 'center', gap: 6, margin: '0 0 10px', fontWeight: 400, fontSize: 'var(--fs-sm)', cursor: 'pointer' }}>
<input type="checkbox" checked={debugFlag} onChange={e => setDebugFlag(e.target.checked)} style={{ width: 'auto' }} />
Ajouter <code>--debug</code> (recommandé en cas de problème — voir Dépannage ci-dessous)
</label>
<div style={{ margin: '0 0 10px' }}>
<label style={{ display: 'flex', alignItems: 'center', gap: 6, fontWeight: 400, fontSize: 'var(--fs-sm)', cursor: 'pointer' }}>
<input type="checkbox" checked={debugFlag} onChange={e => setDebugFlag(e.target.checked)} style={{ width: 'auto' }} />
Ajouter <code>--debug</code>
</label>
<p style={{ margin: '2px 0 0 22px', fontSize: 'var(--fs-sm)', color: 'var(--text-muted)' }}>
Recommandé en cas de problème — voir Dépannage ci-dessous.
</p>
</div>
{!isLocal && (
<div style={{ margin: '0 0 10px' }}>
<label style={{ display: 'flex', alignItems: 'center', gap: 6, fontWeight: 400, fontSize: 'var(--fs-sm)', cursor: 'pointer' }}>
<input type="checkbox" checked={systemCaFlag} onChange={e => setSystemCaFlag(e.target.checked)} style={{ width: 'auto' }} />
Ajouter <code>NODE_OPTIONS=--use-system-ca</code>
</label>
<p style={{ margin: '2px 0 0 22px', fontSize: 'var(--fs-sm)', color: 'var(--text-muted)' }}>
Coché par défaut : nécessaire si un antivirus ou un proxy intercepte le HTTPS — voir Dépannage
ci-dessous. Décochez uniquement si vous savez que ce n'est pas votre cas.
</p>
</div>
)}
<CopyBlock text={configJson} />
<h4 style={{ margin: '20px 0 6px', fontSize: 'var(--fs-sm)' }}>4. Redémarrer Claude Desktop</h4>
@@ -1307,12 +1352,22 @@ function McpServerSection({ goToApiKeys }) {
</p>
<h4 style={{ margin: '0 0 6px', fontSize: 'var(--fs-sm)' }}>5. Vérifier</h4>
<p style={{ margin: '0 0 20px', fontSize: 'var(--fs-sm)', color: 'var(--text-muted)' }}>
Dans la liste des outils MCP de Claude Desktop, les outils <code>crowdlending_*</code> doivent
apparaître (6 en production, 7 en développement local si <code>crowdlending_fetch_url</code> est
activé voir <code>mcp-server/README.md</code>). Testez avec une question du type « Quel est mon
encours de crowdlending actuellement ? ».
<p style={{ margin: '0 0 10px', fontSize: 'var(--fs-sm)', color: 'var(--text-muted)' }}>
Demandez à Claude : « <strong style={{ color: 'var(--text)' }}>Peux-tu me lister les outils auxquels
tu as accès ?</strong> » — c'est la façon la plus fiable de vérifier que la connexion fonctionne
vraiment (plutôt qu'une question sur vos données, qui peut échouer pour d'autres raisons même si la
connexion est bonne). Les outils <code>crowdlending_*</code> doivent apparaître dans sa réponse
{isLocal
? <> (6, ou 7 si <code>crowdlending_fetch_url</code> est activé voir <code>mcp-server/README.md</code>)</>
: ' (6 au total)'}.
</p>
<p style={{ margin: '0 0 20px', fontSize: 'var(--fs-sm)', color: 'var(--text-muted)' }}>
Une fois la connexion confirmée, consultez la FAQ « Comment utiliser le serveur MCP » pour des
exemples de questions et le détail de chaque fonction.
</p>
<button type="button" className="ghost" onClick={() => navigate('/aide?section=faq')} style={{ marginTop: -12, marginBottom: 20 }}>
Voir la FAQ
</button>
<h4 style={{ margin: '0 0 6px', fontSize: 'var(--fs-sm)' }}>6. Dépannage</h4>
@@ -1341,13 +1396,40 @@ function McpServerSection({ goToApiKeys }) {
l'étape 3 (essayez l'autre si l'un des deux est vide ou ne se met pas à jour).
</p>
<p style={{ margin: '0 0 4px', fontSize: 'var(--fs-sm)', fontWeight: 600 }}>Vérifier côté serveur</p>
<p style={{ margin: 0, fontSize: 'var(--fs-sm)', color: 'var(--text-muted)' }}>
La console du serveur (le terminal où tourne <code>npm run dev</code>) affiche désormais une ligne
par requête reçue — session, outil appelé, statut, durée. Si rien n'y apparaît alors qu'un appel a
été fait depuis Claude Desktop, la requête n'arrive jamais jusqu'ici : le problème est côté
<code>npx</code>/<code>mcp-remote</code> (voir ci-dessus), pas dans <code>server.js</code>.
</p>
{!isLocal && (
<>
<p style={{ margin: '0 0 4px', fontSize: 'var(--fs-sm)', fontWeight: 600 }}>
Connexion refusée avec une erreur de certificat, alors que le serveur répond bien
</p>
<p style={{ margin: '0 0 14px', fontSize: 'var(--fs-sm)', color: 'var(--text-muted)' }}>
Symptôme différent du précédent : la config est correcte, mais <code>mcp-remote</code> échoue à
joindre l'URL de production elle-même (pas le registre npm cette fois) avec une erreur
<code> UNABLE_TO_VERIFY_LEAF_SIGNATURE</code> dans les logs. Même cause de fond un antivirus ou
un proxy avec inspection HTTPS (Avast, Kaspersky, ESET) re-signe le trafic avec son propre
certificat, que Node.js ne reconnaît pas (contrairement à votre navigateur, qui lui fait
confiance via le magasin Windows). Solution : cochez la case <code>NODE_OPTIONS=--use-system-ca</code>
ci-dessus (étape 3) Node utilisera alors le magasin de certificats Windows plutôt que le sien.
Nécessite Node.js 22.16 ou plus récent (vérifiable avec <code>node -v</code>) ; sur une version
plus ancienne, il faut exporter le certificat racine de l'antivirus (<code>certmgr.msc</code> →
Autorités de certification racines de confiance → Exporter en Base-64 X.509) et le référencer via
<code> NODE_EXTRA_CA_CERTS</code> à la place. Évitez de désactiver complètement la vérification
TLS (<code>NODE_TLS_REJECT_UNAUTHORIZED=0</code>) : cette connexion sort sur internet avec votre
clé API et vos données, contrairement au développement local en <code>localhost</code>.
</p>
</>
)}
{isLocal && (
<>
<p style={{ margin: '0 0 4px', fontSize: 'var(--fs-sm)', fontWeight: 600 }}>Vérifier côté serveur</p>
<p style={{ margin: 0, fontSize: 'var(--fs-sm)', color: 'var(--text-muted)' }}>
La console du serveur (le terminal où tourne <code>npm run dev</code>) affiche désormais une ligne
par requête reçue — session, outil appelé, statut, durée. Si rien n'y apparaît alors qu'un appel a
été fait depuis Claude Desktop, la requête n'arrive jamais jusqu'ici : le problème est côté
<code>npx</code>/<code>mcp-remote</code> (voir ci-dessus), pas dans <code>server.js</code>.
</p>
</>
)}
</div>
);
}