fix : correctif mr

This commit is contained in:
2026-03-13 09:47:09 +01:00
parent b3fc6f77b1
commit 00dc2daa3d
11 changed files with 61 additions and 67 deletions

View File

@@ -62,7 +62,7 @@
import {Icon as IconifyIcon} from "@iconify/vue"
import CircleSkeleton from "~/components/skeleton/CircleSkeleton.vue"
import TextSkeleton from "~/components/skeleton/TextSkeleton.vue"
import { downloadApiFile, useApiAuthHeader } from "~/composables/useApiAuth"
import { apiFetch, downloadApiFile } from "~/composables/useApiAuth"
const props = defineProps<{
folder: string | null
@@ -71,7 +71,6 @@ const props = defineProps<{
const backups = ref<string[]>([])
const loading = ref(false)
const errorMessage = ref("")
const apiAuthHeader = useApiAuthHeader()
const title = computed(() => {
if (!props.folder) return "Fichiers"
return `Backup — ${props.folder.toUpperCase()}`
@@ -101,10 +100,7 @@ watch(() => props.folder, async (folder) => {
loading.value = true
errorMessage.value = ""
try {
const data = await $fetch<string[]>(`/api/backups?folder=${encodeURIComponent(folder)}`, {
headers: apiAuthHeader,
server: false
})
const data = await apiFetch<string[]>(`/api/backups?folder=${encodeURIComponent(folder)}`)
backups.value = data
} catch (error) {
console.error("Erreur récupération backups:", error)

View File

@@ -79,7 +79,7 @@
<script setup lang="ts">
import { computed, onMounted, ref } from "vue"
import { Icon as IconifyIcon } from "@iconify/vue"
import { useApiAuthHeader } from "~/composables/useApiAuth"
import { apiFetch } from "~/composables/useApiAuth"
type BackupScript = {
key: string
@@ -119,7 +119,6 @@ const scripts = ref<BackupScript[]>([])
const output = ref<string>("")
const message = ref<string>("")
const isError = ref(false)
const apiAuthHeader = useApiAuthHeader()
const statusClass = computed(() => (isError.value ? "status-error" : "status-success"))
@@ -136,9 +135,7 @@ const loadScripts = async () => {
downloadFolders: []
})
try {
const data = await $fetch<BackupScriptListResponse>("/api/backup-script", {
headers: apiAuthHeader
})
const data = await apiFetch<BackupScriptListResponse>("/api/backup-script")
scripts.value = data.scripts
} catch (error) {
scripts.value = []
@@ -164,10 +161,9 @@ const runScript = async (key: string) => {
isError.value = false
try {
const data = await $fetch<BackupScriptRunResponse>("/api/backup-script", {
const data = await apiFetch<BackupScriptRunResponse>("/api/backup-script", {
method: "POST",
body: { key },
headers: apiAuthHeader
body: { key }
})
message.value = `${data.label} execute avec succes`
output.value = data.output || "Aucune sortie retournee."

View File

@@ -1,9 +1,9 @@
<script setup>
import {Icon as IconifyIcon} from "@iconify/vue"
import { useApiAuthHeader } from "~/composables/useApiAuth"
import { apiFetch } from "~/composables/useApiAuth"
const { data: messages, error } = await useFetch('/api/discord/messages', {
headers: useApiAuthHeader(),
$fetch: apiFetch,
server: false
})
</script>

View File

@@ -46,14 +46,13 @@
<script setup lang="ts">
import {computed, ref} from "vue"
import {Icon as IconifyIcon} from "@iconify/vue"
import { useApiAuthHeader, withApiAuth } from "~/composables/useApiAuth"
import { apiRequest } from "~/composables/useApiAuth"
const ping = ref<number | null>(null)
const download = ref<number | null>(null)
const upload = ref<number | null>(null)
const isTesting = ref(false)
const errorMessage = ref("")
const apiAuthHeader = useApiAuthHeader()
const metrics = computed(() => [
{ label: "Download", icon: "mdi:arrow-down-bold", value: download.value, unit: "Mbps" },
@@ -63,9 +62,7 @@ const metrics = computed(() => [
async function testDownload() {
const start = performance.now()
const res = await fetch('/api/download', {
headers: apiAuthHeader
})
const res = await apiRequest('/api/download')
if (!res.ok) {
throw new Error(`HTTP ${res.status}`)
}
@@ -80,7 +77,7 @@ async function testUpload() {
const size = 5 * 1024 * 1024
const data = new Uint8Array(size)
const start = performance.now()
const response = await fetch('/api/upload', withApiAuth({ method: 'POST', body: data }))
const response = await apiRequest('/api/upload', { method: 'POST', body: data })
if (!response.ok) {
throw new Error(`HTTP ${response.status}`)
}

View File

@@ -43,7 +43,7 @@
import CircleSkeleton from "~/components/skeleton/CircleSkeleton.vue"
import TextSkeleton from "~/components/skeleton/TextSkeleton.vue"
import {onBeforeUnmount, onMounted, ref} from "vue"
import { useApiAuthHeader } from "~/composables/useApiAuth"
import { apiFetch } from "~/composables/useApiAuth"
interface StatusRow {
label: string
@@ -72,7 +72,6 @@ const props = withDefaults(
const rows = ref<StatusRow[]>([])
const loading = ref(true)
const initialized = ref(false)
const apiAuthHeader = useApiAuthHeader()
let timer: ReturnType<typeof setInterval> | null = null
const statusLabel = (status: number) => {
@@ -86,9 +85,7 @@ const checkStatus = async () => {
loading.value = true
}
try {
const data = await $fetch<StatusResponse>(props.endpoint, {
headers: apiAuthHeader
})
const data = await apiFetch<StatusResponse>(props.endpoint)
rows.value = data.results
} catch (error) {
rows.value = [

View File

@@ -32,26 +32,26 @@ function getDownloadFileName(contentDisposition: string | null, fallback: string
return fallback
}
export function useApiAuthHeader() {
const runtimeConfig = useRuntimeConfig()
const token = runtimeConfig.public.apiSecretKey
if (!token) {
return {}
}
// Tous les appels frontend vers /api/* reutilisent ce header commun.
export function withApiAuth(init: RequestInit = {}) {
// Les appels frontend reutilisent les cookies httpOnly poses cote serveur.
return {
Authorization: `Bearer ${token}`
...init,
headers: {
...toHeadersObject(init.headers)
}
}
}
export const apiFetch = $fetch.create({})
export function apiRequest(input: RequestInfo | URL, init: RequestInit = {}) {
return fetch(input, withApiAuth(init))
}
export async function downloadApiFile(url: string, fileNameFallback: string) {
// Les telechargements passent aussi par fetch pour pouvoir envoyer
// le header Authorization, contrairement a un simple <a href>.
const response = await fetch(url, {
headers: useApiAuthHeader()
})
// Les telechargements passent aussi par fetch pour pouvoir recuperer
// le contenu et le nom de fichier renvoye par l'API.
const response = await apiRequest(url)
if (!response.ok) {
throw new Error(`HTTP ${response.status}`)
@@ -73,14 +73,3 @@ export async function downloadApiFile(url: string, fileNameFallback: string) {
link.remove()
URL.revokeObjectURL(objectUrl)
}
export function withApiAuth(init: RequestInit = {}) {
// Fusionne le header d'auth avec d'eventuels headers deja fournis.
return {
...init,
headers: {
...useApiAuthHeader(),
...toHeadersObject(init.headers)
}
}
}

View File

@@ -37,7 +37,6 @@ export default defineNuxtConfig({
runtimeConfig: {
apiSecretKey: process.env.API_SECRET_KEY,
public: {
apiSecretKey: process.env.API_SECRET_KEY || "",
appVersion: getRepoVersion()
}
},

View File

@@ -96,7 +96,7 @@
<script setup lang="ts">
import { ref } from "vue"
import BackupRun from "~/components/BackupRun.vue"
import { downloadApiFile, useApiAuthHeader } from "~/composables/useApiAuth"
import { apiFetch, downloadApiFile } from "~/composables/useApiAuth"
definePageMeta({ layout: false })
@@ -118,12 +118,9 @@ const emptyScriptResult = (): ScriptResult => ({
const selectedBackup = ref<string | null>(null)
const scriptResult = ref<ScriptResult>(emptyScriptResult())
const apiAuthHeader = useApiAuthHeader()
const fetchLatestBackup = async (folder: string) => {
const files = await $fetch<string[]>(`/api/backups?folder=${encodeURIComponent(folder)}`, {
headers: apiAuthHeader
})
const files = await apiFetch<string[]>(`/api/backups?folder=${encodeURIComponent(folder)}`)
return files[0] || null
}

View File

@@ -49,7 +49,7 @@
<script setup lang="ts">
definePageMeta({layout: false})
import {computed, onMounted, ref} from "vue"
import { useApiAuthHeader } from "~/composables/useApiAuth"
import { apiFetch } from "~/composables/useApiAuth"
type DiskSourceResult = {
key: string
@@ -78,7 +78,6 @@ type DiagramItem = {
const selectedBackup = ref<string | null>(null)
const rawResults = ref<DiskSourceResult[]>([])
const loading = ref(false)
const apiAuthHeader = useApiAuthHeader()
const chartRadius = 52
const chartCircumference = 2 * Math.PI * chartRadius
@@ -153,9 +152,7 @@ const runScript = async () => {
rawResults.value = []
try {
const output = await $fetch<DiskApiResponse>("/api/disk", {
headers: apiAuthHeader
})
const output = await apiFetch<DiskApiResponse>("/api/disk")
rawResults.value = output.results
} catch (error) {
rawResults.value = [

View File

@@ -0,0 +1,25 @@
export default defineEventHandler((event) => {
const path = event.path || event.node.req.url || ""
if (path.startsWith("/api/")) {
return
}
const runtimeConfig = useRuntimeConfig(event)
const expectedToken = runtimeConfig.apiSecretKey
if (!expectedToken) {
return
}
if (getCookie(event, "api_auth_token") === expectedToken) {
return
}
setCookie(event, "api_auth_token", expectedToken, {
httpOnly: true,
sameSite: "lax",
secure: process.env.NODE_ENV === "production",
path: "/"
})
})

View File

@@ -9,6 +9,7 @@ export default defineEventHandler((event) => {
const runtimeConfig = useRuntimeConfig(event)
const authorization = getHeader(event, "authorization")
const cookieToken = getCookie(event, "api_auth_token")
const expectedToken = runtimeConfig.apiSecretKey
// Si aucun secret n'est configure cote serveur, on refuse la requete.
@@ -19,9 +20,9 @@ export default defineEventHandler((event) => {
})
}
// Le header doit correspondre exactement au format attendu :
// Authorization: Bearer <token>
if (authorization !== `Bearer ${expectedToken}`) {
// Le secret peut venir soit d'un header serveur explicite,
// soit du cookie httpOnly pose pour l'application web.
if (authorization !== `Bearer ${expectedToken}` && cookieToken !== expectedToken) {
throw createError({
statusCode: 401,
statusMessage: "Unauthorized"