@orsetra/shared-auth 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/SessionProvider.tsx +86 -0
- package/hooks/useSession.ts +72 -0
- package/index.ts +5 -1
- package/package.json +4 -2
- package/services/zitadel.auth.service.ts +10 -1
- package/utils/index.ts +1 -1
- package/utils/redirect-utils.ts +45 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { createContext, useContext, useEffect, useState, ReactNode } from "react"
|
|
4
|
+
|
|
5
|
+
interface SessionUser {
|
|
6
|
+
access_token: string
|
|
7
|
+
id_token?: string
|
|
8
|
+
expires_at?: number
|
|
9
|
+
profile: {
|
|
10
|
+
sub: string
|
|
11
|
+
email?: string
|
|
12
|
+
name?: string
|
|
13
|
+
preferred_username?: string
|
|
14
|
+
[key: string]: any
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface SessionContextType {
|
|
19
|
+
user: SessionUser | null
|
|
20
|
+
accessToken: string | null
|
|
21
|
+
isLoading: boolean
|
|
22
|
+
isAuthenticated: boolean
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const SessionContext = createContext<SessionContextType | undefined>(undefined)
|
|
26
|
+
|
|
27
|
+
interface SessionProviderProps {
|
|
28
|
+
children: ReactNode
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Provider de session pour les micro-apps
|
|
33
|
+
* À utiliser après validation par le middleware
|
|
34
|
+
* Lit directement depuis localStorage (pas de configuration requise)
|
|
35
|
+
*/
|
|
36
|
+
export function SessionProvider({ children }: SessionProviderProps) {
|
|
37
|
+
const [user, setUser] = useState<SessionUser | null>(null)
|
|
38
|
+
const [isLoading, setIsLoading] = useState(true)
|
|
39
|
+
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
const authority = process.env.NEXT_PUBLIC_ZITADEL_AUTHORITY
|
|
42
|
+
const clientId = process.env.NEXT_PUBLIC_ZITADEL_CLIENT_ID
|
|
43
|
+
const storageKey = `oidc.user:${authority}:${clientId}`
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
const userDataString = localStorage.getItem(storageKey)
|
|
47
|
+
|
|
48
|
+
if (userDataString) {
|
|
49
|
+
const userData = JSON.parse(userDataString) as SessionUser
|
|
50
|
+
const isExpired = userData.expires_at && userData.expires_at * 1000 < Date.now()
|
|
51
|
+
|
|
52
|
+
if (!isExpired && userData.access_token) {
|
|
53
|
+
setUser(userData)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
} catch (error) {
|
|
57
|
+
console.error('Error reading session:', error)
|
|
58
|
+
} finally {
|
|
59
|
+
setIsLoading(false)
|
|
60
|
+
}
|
|
61
|
+
}, [])
|
|
62
|
+
|
|
63
|
+
const value: SessionContextType = {
|
|
64
|
+
user,
|
|
65
|
+
accessToken: user?.access_token ?? null,
|
|
66
|
+
isLoading,
|
|
67
|
+
isAuthenticated: !!user,
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<SessionContext.Provider value={value}>
|
|
72
|
+
{children}
|
|
73
|
+
</SessionContext.Provider>
|
|
74
|
+
)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Hook pour accéder au contexte de session dans les micro-apps
|
|
79
|
+
*/
|
|
80
|
+
export function useSession(): SessionContextType {
|
|
81
|
+
const context = useContext(SessionContext)
|
|
82
|
+
if (context === undefined) {
|
|
83
|
+
throw new Error("useSession must be used within a SessionProvider")
|
|
84
|
+
}
|
|
85
|
+
return context
|
|
86
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState } from 'react'
|
|
4
|
+
|
|
5
|
+
interface SessionUser {
|
|
6
|
+
access_token: string
|
|
7
|
+
id_token?: string
|
|
8
|
+
expires_at?: number
|
|
9
|
+
profile: {
|
|
10
|
+
sub: string
|
|
11
|
+
email?: string
|
|
12
|
+
name?: string
|
|
13
|
+
preferred_username?: string
|
|
14
|
+
[key: string]: any
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface SessionState {
|
|
19
|
+
user: SessionUser | null
|
|
20
|
+
isLoading: boolean
|
|
21
|
+
isAuthenticated: boolean
|
|
22
|
+
accessToken: string | null
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Hook pour récupérer la session utilisateur dans les micro-apps
|
|
27
|
+
* Lit depuis localStorage (partagé via même domaine)
|
|
28
|
+
*/
|
|
29
|
+
export function useSession(): SessionState {
|
|
30
|
+
const [state, setState] = useState<SessionState>({
|
|
31
|
+
user: null,
|
|
32
|
+
isLoading: true,
|
|
33
|
+
isAuthenticated: false,
|
|
34
|
+
accessToken: null,
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
const authority = process.env.NEXT_PUBLIC_ZITADEL_AUTHORITY
|
|
39
|
+
const clientId = process.env.NEXT_PUBLIC_ZITADEL_CLIENT_ID
|
|
40
|
+
const storageKey = `oidc.user:${authority}:${clientId}`
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
const userDataString = localStorage.getItem(storageKey)
|
|
44
|
+
|
|
45
|
+
if (userDataString) {
|
|
46
|
+
const userData = JSON.parse(userDataString) as SessionUser
|
|
47
|
+
const isExpired = userData.expires_at && userData.expires_at * 1000 < Date.now()
|
|
48
|
+
|
|
49
|
+
if (!isExpired) {
|
|
50
|
+
setState({
|
|
51
|
+
user: userData,
|
|
52
|
+
isLoading: false,
|
|
53
|
+
isAuthenticated: true,
|
|
54
|
+
accessToken: userData.access_token,
|
|
55
|
+
})
|
|
56
|
+
return
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
} catch (error) {
|
|
60
|
+
console.error('Error reading session:', error)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
setState({
|
|
64
|
+
user: null,
|
|
65
|
+
isLoading: false,
|
|
66
|
+
isAuthenticated: false,
|
|
67
|
+
accessToken: null,
|
|
68
|
+
})
|
|
69
|
+
}, [])
|
|
70
|
+
|
|
71
|
+
return state
|
|
72
|
+
}
|
package/index.ts
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
|
-
// Auth exports
|
|
1
|
+
// Auth exports - Main app
|
|
2
2
|
export { ZitadelProvider, useZitadel } from './ZitadelProvider'
|
|
3
3
|
export { ProtectedRoute } from './ProtectedRoute'
|
|
4
4
|
export { ZitadelAuthService } from './services/zitadel.auth.service'
|
|
5
5
|
export { createAuthConfig } from './config/zitadel.config'
|
|
6
6
|
export type { ZitadelConfig } from './config/zitadel.config'
|
|
7
|
+
|
|
8
|
+
// Auth exports - Micro-apps
|
|
9
|
+
export { SessionProvider, useSession } from './SessionProvider'
|
|
10
|
+
|
|
7
11
|
export * from './utils'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orsetra/shared-auth",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Shared authentication utilities for Orsetra platform using Zitadel",
|
|
5
5
|
"main": "./index.ts",
|
|
6
6
|
"types": "./index.ts",
|
|
@@ -9,15 +9,17 @@
|
|
|
9
9
|
"./config": "./config/zitadel.config.ts",
|
|
10
10
|
"./services": "./services/zitadel.auth.service.ts",
|
|
11
11
|
"./components": "./components/index.ts",
|
|
12
|
-
"./utils": "./utils/
|
|
12
|
+
"./utils": "./utils/index.ts"
|
|
13
13
|
},
|
|
14
14
|
"files": [
|
|
15
15
|
"index.ts",
|
|
16
16
|
"ZitadelProvider.tsx",
|
|
17
|
+
"SessionProvider.tsx",
|
|
17
18
|
"ProtectedRoute.tsx",
|
|
18
19
|
"config",
|
|
19
20
|
"services",
|
|
20
21
|
"components",
|
|
22
|
+
"hooks",
|
|
21
23
|
"utils",
|
|
22
24
|
"README.md"
|
|
23
25
|
],
|
|
@@ -51,6 +51,7 @@ export class ZitadelAuthService {
|
|
|
51
51
|
|
|
52
52
|
static async signOut() {
|
|
53
53
|
try {
|
|
54
|
+
document.cookie = 'auth_session=; path=/; max-age=0';
|
|
54
55
|
await this.getAuth().signout();
|
|
55
56
|
} catch (error) {
|
|
56
57
|
throw this.handleAuthError(error);
|
|
@@ -68,7 +69,15 @@ export class ZitadelAuthService {
|
|
|
68
69
|
|
|
69
70
|
static async handleCallback(): Promise<User> {
|
|
70
71
|
try {
|
|
71
|
-
|
|
72
|
+
const user = await this.getAuth().userManager.signinRedirectCallback();
|
|
73
|
+
if (user?.access_token) {
|
|
74
|
+
const maxAge = user.expires_at
|
|
75
|
+
? user.expires_at - Math.floor(Date.now() / 1000)
|
|
76
|
+
: 3600;
|
|
77
|
+
document.cookie = `auth_session=1; path=/; max-age=${maxAge}; SameSite=Lax`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return user;
|
|
72
81
|
} catch (error) {
|
|
73
82
|
throw this.handleAuthError(error);
|
|
74
83
|
}
|
package/utils/index.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export * from './token-validator'
|
|
2
|
-
export * from '
|
|
2
|
+
export * from './redirect-utils'
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utilitaires pour gérer les redirections après authentification
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const REDIRECT_KEY = 'zitadel_redirect_url'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Sauvegarde l'URL actuelle pour redirection après authentification
|
|
9
|
+
*/
|
|
10
|
+
export function saveRedirectUrl(url?: string): void {
|
|
11
|
+
if (typeof window === 'undefined') return
|
|
12
|
+
|
|
13
|
+
const urlToSave = url || window.location.pathname + window.location.search
|
|
14
|
+
sessionStorage.setItem(REDIRECT_KEY, urlToSave)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Récupère et supprime l'URL de redirection sauvegardée
|
|
19
|
+
* @param fallback URL par défaut si aucune redirection n'est sauvegardée
|
|
20
|
+
* @returns L'URL de redirection
|
|
21
|
+
*/
|
|
22
|
+
export function getAndClearRedirectUrl(fallback: string = ''): string {
|
|
23
|
+
if (typeof window === 'undefined') return fallback
|
|
24
|
+
|
|
25
|
+
const redirectUrl = sessionStorage.getItem(REDIRECT_KEY)
|
|
26
|
+
sessionStorage.removeItem(REDIRECT_KEY)
|
|
27
|
+
|
|
28
|
+
return redirectUrl || fallback
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Vérifie si une URL de redirection est sauvegardée
|
|
33
|
+
*/
|
|
34
|
+
export function hasRedirectUrl(): boolean {
|
|
35
|
+
if (typeof window === 'undefined') return false
|
|
36
|
+
return sessionStorage.getItem(REDIRECT_KEY) !== null
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Nettoie l'URL de redirection sauvegardée
|
|
41
|
+
*/
|
|
42
|
+
export function clearRedirectUrl(): void {
|
|
43
|
+
if (typeof window === 'undefined') return
|
|
44
|
+
sessionStorage.removeItem(REDIRECT_KEY)
|
|
45
|
+
}
|