feat: PKCE auth + CI/CD deploy
Some checks failed
CI/CD — Build & Deploy / Build & Deploy (push) Failing after 25s

- Frontend: PKCE flow (oauth.js, api.js centralized, cookie-based AuthContext)
- Backend: token introspection, cookies httpOnly, refresh endpoint
- Replaced localStorage JWT with httpOnly session cookies
- useSaveSync migrated to cookie auth
- cookie-parser added
- Gitea CI workflow (vps-runner pattern)
This commit is contained in:
2026-03-24 13:01:15 +01:00
parent 39f683a31e
commit 91d1616dd7
15 changed files with 548 additions and 393 deletions

View File

@@ -1,26 +1,60 @@
import { useEffect, useState } from "react";
import { useNavigate, useSearchParams, Link } from "react-router-dom";
import { useEffect, useState, useRef } from "react";
import { useNavigate, Link } from "react-router-dom";
import { exchangeCode, loadVerifier, clearVerifier } from "../lib/oauth";
import { apiFetch } from "../lib/api";
import { useAuth } from "../context/AuthContext";
import "../scss/pages.scss";
export default function AuthCallback() {
const [searchParams] = useSearchParams();
const { loginWithOAuth } = useAuth();
const navigate = useNavigate();
const { refresh } = useAuth();
const [error, setError] = useState(null);
const called = useRef(false);
useEffect(() => {
const token = searchParams.get("token");
if (called.current) return;
called.current = true;
if (!token) {
setError("Token manquant dans l'URL.");
const params = new URLSearchParams(window.location.search);
const code = params.get("code");
const err = params.get("error");
if (err) {
setError(err);
return;
}
loginWithOAuth(token)
if (!code) {
setError("Code manquant dans l'URL.");
return;
}
const verifier = loadVerifier();
if (!verifier) {
setError("Verifier PKCE manquant — réessaie la connexion.");
return;
}
const redirectUri = `${window.location.origin}/callback`;
exchangeCode(code, verifier, redirectUri)
.then((tokens) => {
clearVerifier();
return apiFetch("/auth/session", {
method: "POST",
body: JSON.stringify({
token: tokens.access_token,
refreshToken: tokens.refresh_token,
}),
});
})
.then(() => refresh())
.then(() => navigate("/", { replace: true }))
.catch((err) => setError(err.message || "Erreur de connexion."));
}, []);
.catch((e) => {
clearVerifier();
setError(e.message || "Erreur de connexion.");
});
}, [navigate, refresh]);
if (error) {
return (