Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,13 @@ jobs:
type=semver,pattern={{version}}
type=sha,enable=false

- name: Create env file
run: |
echo "NEXT_PUBLIC_OIDC_CLIENT_ID=${{ vars.NEXT_PUBLIC_OIDC_CLIENT_ID }}" >> .env.production
echo "NEXT_PUBLIC_OIDC_PROVIDER_URL=${{ vars.NEXT_PUBLIC_OIDC_PROVIDER_URL }}" >> .env.production
echo "NEXT_PUBLIC_OIDC_REDIRECT_URI=${{ vars.NEXT_PUBLIC_OIDC_REDIRECT_URI }}" >> .env.production
echo "SHARING_API=${{ vars.SHARING_API }}" >> .env.production

- name: Build and push Docker image
id: push
uses: docker/build-push-action@v5
Expand Down Expand Up @@ -82,4 +89,4 @@ jobs:
git config --global user.email "github-actions[bot]@users.noreply.github.com"
git config --global user.name "github-actions[bot]"
git commit -m "Update app version to ${{ github.event.release.tag_name }}"
git push origin main
git push origin main
10 changes: 6 additions & 4 deletions app/actions/sharing.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
'use server'

import { apiClient } from '@/lib/api-client'

// Types pour l'API
export interface PostSecretRequest {
content: Record<string, string>
Expand Down Expand Up @@ -41,7 +43,7 @@ export async function createSecret(request: PostSecretRequest): Promise<PostSecr
console.log('Données envoyées:', JSON.stringify(request, null, 2))

try {
const response = await fetch(apiUrl, {
const response = await apiClient.fetch(apiUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Expand Down Expand Up @@ -75,7 +77,7 @@ export async function getSecret(id: string): Promise<GetSecretResponse> {
console.log(`Récupération du secret ${id} depuis ${apiUrl}/${id}`)

try {
const response = await fetch(`${apiUrl}/${id}`, {
const response = await apiClient.fetch(`${apiUrl}/${id}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
Expand Down Expand Up @@ -114,7 +116,7 @@ export async function decryptSecret(id: string, passphrase: string): Promise<Get
console.log(`Déchiffrement du secret ${id}`)

try {
const response = await fetch(`${apiUrl}/${id}`, {
const response = await apiClient.fetch(`${apiUrl}/${id}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Expand Down Expand Up @@ -154,7 +156,7 @@ export async function getHistory(): Promise<HistorySecret[]> {
console.log(`Récupération de l'historique depuis ${apiUrl}/history`)

try {
const response = await fetch(`${apiUrl}/history`, {
const response = await apiClient.fetch(`${apiUrl}/history`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
Expand Down
3 changes: 3 additions & 0 deletions app/auth/callback/loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function Loading() {
return null
}
94 changes: 94 additions & 0 deletions app/auth/callback/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
'use client'

import { useEffect, useState } from 'react'
import { useRouter, useSearchParams } from 'next/navigation'
import { exchangeCodeForTokens } from '@/lib/auth'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'
import { AlertCircle, CheckCircle2 } from 'lucide-react'

export default function AuthCallbackPage() {
const router = useRouter()
const searchParams = useSearchParams()
const [status, setStatus] = useState<'loading' | 'success' | 'error'>('loading')
const [error, setError] = useState<string | null>(null)

useEffect(() => {
const handleCallback = async () => {
try {
const code = searchParams.get('code')
const state = searchParams.get('state')
const errorParam = searchParams.get('error')

if (errorParam) {
setError(`Erreur d'authentification: ${errorParam}`)
setStatus('error')
return
}

if (!code || !state) {
setError('Paramètres de callback manquants')
setStatus('error')
return
}

const success = await exchangeCodeForTokens(code, state)

if (success) {
setStatus('success')
// Rediriger vers la page d'accueil après un court délai
setTimeout(() => {
router.push('/')
}, 2000)
} else {
setError("Échec de l'échange du code d'autorisation")
setStatus('error')
}
} catch (error) {
console.error('Erreur dans le callback:', error)
setError("Une erreur inattendue s'est produite")
setStatus('error')
}
}

handleCallback()
}, [searchParams, router])

return (
<div className="flex min-h-screen flex-col items-center justify-center p-6">
<Card className="w-full max-w-md">
<CardHeader>
<CardTitle className="text-center">
{status === 'loading' && 'Authentification en cours...'}
{status === 'success' && 'Connexion réussie'}
{status === 'error' && "Erreur d'authentification"}
</CardTitle>
<CardDescription className="text-center">
{status === 'loading' && 'Veuillez patienter pendant que nous finalisons votre connexion.'}
{status === 'success' && "Vous allez être redirigé vers l'application."}
{status === 'error' && "Une erreur s'est produite lors de l'authentification."}
</CardDescription>
</CardHeader>
<CardContent className="flex flex-col items-center space-y-4">
{status === 'loading' && <div className="animate-spin rounded-full h-12 w-12 border-b-2 border-primary"></div>}

{status === 'success' && (
<Alert>
<CheckCircle2 className="h-4 w-4" />
<AlertTitle>Succès</AlertTitle>
<AlertDescription>Vous êtes maintenant connecté. Redirection en cours...</AlertDescription>
</Alert>
)}

{status === 'error' && (
<Alert variant="destructive">
<AlertCircle className="h-4 w-4" />
<AlertTitle>Erreur</AlertTitle>
<AlertDescription>{error || "Une erreur inconnue s'est produite."}</AlertDescription>
</Alert>
)}
</CardContent>
</Card>
</div>
)
}
18 changes: 10 additions & 8 deletions app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
import type { Metadata } from 'next'
import type React from 'react'
import { Toaster } from '@/components/ui/toaster'
import './globals.css'
import { Metadata } from 'next'
import { AuthGuard } from '@/components/auth-guard'

export const metadata: Metadata = {
title: 'v0 App',
description: 'Created with v0',
generator: 'v0.dev',
}

export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode
}>) {
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>{children}</body>
<html lang="fr">
<body>
<AuthGuard>{children}</AuthGuard>
<Toaster />
</body>
</html>
)
}
49 changes: 49 additions & 0 deletions components/auth-guard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
'use client'

import type React from 'react'

import { useEffect } from 'react'
import { useAuthStore } from '@/lib/auth'
import { initiateOIDCLogin } from '@/lib/auth'
import { usePathname } from 'next/navigation'

interface AuthGuardProps {
children: React.ReactNode
}

export function AuthGuard({ children }: AuthGuardProps) {
const { isAuthenticated, accessToken } = useAuthStore()

const pathname = usePathname() // Get current path

useEffect(() => {
// Skip guard for /auth/callback
if (pathname === '/auth/callback') {
return
}
// Si pas d'access token, rediriger vers l'identity provider
if (!accessToken) {
initiateOIDCLogin()
}
}, [accessToken, pathname])

// Skip guard for /auth/callback
if (pathname === '/auth/callback') {
return <>{children}</>
}

// Afficher un loader pendant la redirection
if (!isAuthenticated || !accessToken) {
return (
<div className="flex min-h-screen flex-col items-center justify-center p-6">
<div className="text-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-primary mx-auto mb-4"></div>
<h2 className="text-xl font-semibold mb-2">Connexion en cours...</h2>
<p className="text-muted-foreground">Redirection vers le fournisseur d'identité...</p>
</div>
</div>
)
}

return <>{children}</>
}
Loading