@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.
@@ -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.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/redirect-utils.ts"
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
- return await this.getAuth().userManager.signinRedirectCallback();
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 '../redirect-utils'
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
+ }