codevdesign 1.0.23 → 1.0.24

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.
@@ -1,186 +1,186 @@
1
- class RafraichisseurToken {
2
- private intervalleEnSecondes = 15
3
- private secondesAvantExpirationTokenPourRafraichir = 20
4
- private skewSeconds = 5 // marge anti-derives d’horloge
5
- private popupAffiche = false
6
- private timerId: number | null = null
7
-
8
- // Lance une seule fois
9
- public async demarrer(nomTemoin: string | null, urlPortail: string): Promise<void> {
10
- if (nomTemoin == null || nomTemoin === '') nomTemoin = 'csqc_jeton_secure_expiration'
11
- await this.verifierJeton(nomTemoin, urlPortail)
12
- if (this.timerId != null) return
13
- this.timerId = window.setInterval(() => {
14
- this.verifierJeton(nomTemoin, urlPortail)
15
- }, this.intervalleEnSecondes * 1000)
16
- }
17
-
18
- // Permet d’arrêter le timer (ex: au logout / destroy)
19
- public arreter(): void {
20
- if (this.timerId != null) {
21
- clearInterval(this.timerId)
22
- this.timerId = null
23
- }
24
- }
25
-
26
- public existeJeton = (nomTemoin: string) => {
27
- return this.estJetonValide(nomTemoin)
28
- }
29
-
30
- private async verifierJeton(nomTemoin: string, urlPortail: string): Promise<void> {
31
- if (this.popupAffiche) return
32
-
33
- if (!this.estJetonValide(nomTemoin)) {
34
- this.rafraichir(nomTemoin, urlPortail)
35
- return
36
- }
37
- }
38
-
39
- private estJetonValide(nomTemoin: string): boolean {
40
- if (this.popupAffiche || !nomTemoin) return true //On fait semblant que c'est valide pour ne pas provoquer un autre affichage du popup.
41
-
42
- const tokenEncode = this.lireCookie(nomTemoin)
43
- if (!tokenEncode) {
44
- return false
45
- }
46
-
47
- let token: any
48
- try {
49
- token = this.parseJwt(tokenEncode)
50
- } catch {
51
- return false
52
- }
53
-
54
- const exp = Number(token?.exp)
55
- if (!Number.isFinite(exp)) {
56
- // exp manquant/invalide → tente refresh
57
-
58
- return false
59
- }
60
-
61
- const now = Math.floor(Date.now() / 1000)
62
- const refreshAt = exp - this.secondesAvantExpirationTokenPourRafraichir - this.skewSeconds
63
- if (now >= refreshAt) {
64
- return false
65
- }
66
-
67
- return true
68
- }
69
-
70
- private async rafraichir(nomCookie: string, urlPortail: string): Promise<void> {
71
- if (!nomCookie) return
72
- const url = this.getRefreshUrl(urlPortail)
73
- const controller = new AbortController()
74
- const timeout = setTimeout(() => controller.abort(), 10_000)
75
-
76
- try {
77
- //Première tentative sans iframe, pour la majorité des cas.
78
- const resp = await fetch(url, {
79
- method: 'POST',
80
- credentials: 'include',
81
- headers: { Accept: 'application/json' },
82
- redirect: 'manual',
83
- signal: controller.signal,
84
- })
85
-
86
- // redirection (souvent => login) → traiter comme non auth
87
-
88
- if (resp.type === 'opaqueredirect' || resp.status === 302) {
89
- this.rafraichirParIframe(nomCookie, urlPortail)
90
- return
91
- }
92
-
93
- // OK ou No Content: le cookie devrait être réécrit
94
- if (resp.status === 200 || resp.status === 204) {
95
- const jeton = this.lireCookie(nomCookie)
96
- if (!jeton) this.rafraichirParIframe(nomCookie, urlPortail)
97
- return
98
- }
99
-
100
- // non auth / expiré (401, 419) + IIS timeout (440)
101
- if (resp.status === 401 || resp.status === 419 || resp.status === 440) {
102
- this.rafraichirParIframe(nomCookie, urlPortail)
103
- return
104
- }
105
-
106
- console.warn('Rafraichisseur token: statut inattendu', resp.status)
107
- } catch (err: any) {
108
- if (err?.name === 'AbortError') console.warn('RafraichisseurToken timeout')
109
- else console.error('Erreur rafraichisseur de token', err)
110
- // on réessaiera au prochain tick
111
- } finally {
112
- clearTimeout(timeout)
113
- }
114
- }
115
-
116
- private rafraichirParIframe(nomCookie: string, urlPortail: string): void {
117
- // Pour éviter les cross référence, on créé un iframe qui appel portail et force la MAJ du jeton ou l'invalidation du jeton, si jamais l'utilisateur n'est plus connecté
118
- // ajax vers le refresh
119
- let iframe = document.createElement('iframe')
120
- const url = this.getRefreshUrl(urlPortail)
121
- iframe.src = `${url}?urlRetour=${encodeURI(window.localStorage.href)}`
122
- iframe.id = 'idRafrToken'
123
- iframe.style.display = 'none'
124
- document.body.appendChild(iframe)
125
- iframe.onload = () => {
126
- const jetonCSQC = this.lireCookie(nomCookie)
127
- if (jetonCSQC == null || jetonCSQC === '') {
128
- this.estDeconnecteAzure(urlPortail)
129
- }
130
-
131
- iframe.remove()
132
- }
133
- }
134
-
135
- private estDeconnecteAzure(urlPortail: string): void {
136
- //on envoie au portail, pas le choix
137
-
138
- const retour = encodeURI(window.location.href)
139
- window.open(`${urlPortail}/home/SeConnecter?urlRetour=${retour}`, '_self')
140
- return
141
- }
142
-
143
- public deconnecterPortail(urlPortail: string): void {
144
- window.location.replace(`${urlPortail}deconnecte?urlRetour=${encodeURIComponent(window.location.href)}`)
145
- }
146
-
147
- private lireCookie(nom: string): string | null {
148
- if (!document.cookie) return null
149
- const cookies = document.cookie.split(';').map(c => c.trim())
150
- for (const cookie of cookies) {
151
- if (cookie.startsWith(`${nom}=`)) {
152
- try {
153
- return decodeURIComponent(cookie.substring(nom.length + 1))
154
- } catch {
155
- return cookie.substring(nom.length + 1)
156
- }
157
- }
158
- }
159
- return null
160
- }
161
-
162
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
163
- private parseJwt(token: string): any {
164
- const parts = token.split('.')
165
- const base64Url = parts[1]
166
- if (!base64Url) throw new Error('Invalid JWT format')
167
- const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/')
168
- const jsonPayload = decodeURIComponent(
169
- atob(base64)
170
- .split('')
171
- .map(c => `%${`00${c.charCodeAt(0).toString(16)}`.slice(-2)}`)
172
- .join(''),
173
- )
174
- return JSON.parse(jsonPayload)
175
- }
176
-
177
- // URL refresh selon env (dev = proxy Vite ; prod = portail)
178
- private getRefreshUrl(urlPortail: string): string {
179
- if (import.meta.env.MODE === 'development') return '/portail-refresh'
180
- return urlPortail + '/Home/Refresh'
181
- }
182
- }
183
-
184
- // Instance
185
- const rafraichisseurToken = new RafraichisseurToken()
186
- export default rafraichisseurToken
1
+ class RafraichisseurToken {
2
+ private intervalleEnSecondes = 15
3
+ private secondesAvantExpirationTokenPourRafraichir = 20
4
+ private skewSeconds = 5 // marge anti-derives d’horloge
5
+ private popupAffiche = false
6
+ private timerId: number | null = null
7
+
8
+ // Lance une seule fois
9
+ public async demarrer(nomTemoin: string | null, urlPortail: string): Promise<void> {
10
+ if (nomTemoin == null || nomTemoin === '') nomTemoin = 'csqc_jeton_secure_expiration'
11
+ await this.verifierJeton(nomTemoin, urlPortail)
12
+ if (this.timerId != null) return
13
+ this.timerId = window.setInterval(() => {
14
+ this.verifierJeton(nomTemoin, urlPortail)
15
+ }, this.intervalleEnSecondes * 1000)
16
+ }
17
+
18
+ // Permet d’arrêter le timer (ex: au logout / destroy)
19
+ public arreter(): void {
20
+ if (this.timerId != null) {
21
+ clearInterval(this.timerId)
22
+ this.timerId = null
23
+ }
24
+ }
25
+
26
+ public existeJeton = (nomTemoin: string) => {
27
+ return this.estJetonValide(nomTemoin)
28
+ }
29
+
30
+ private async verifierJeton(nomTemoin: string, urlPortail: string): Promise<void> {
31
+ if (this.popupAffiche) return
32
+
33
+ if (!this.estJetonValide(nomTemoin)) {
34
+ this.rafraichir(nomTemoin, urlPortail)
35
+ return
36
+ }
37
+ }
38
+
39
+ private estJetonValide(nomTemoin: string): boolean {
40
+ if (this.popupAffiche || !nomTemoin) return true //On fait semblant que c'est valide pour ne pas provoquer un autre affichage du popup.
41
+
42
+ const tokenEncode = this.lireCookie(nomTemoin)
43
+ if (!tokenEncode) {
44
+ return false
45
+ }
46
+
47
+ let token: any
48
+ try {
49
+ token = this.parseJwt(tokenEncode)
50
+ } catch {
51
+ return false
52
+ }
53
+
54
+ const exp = Number(token?.exp)
55
+ if (!Number.isFinite(exp)) {
56
+ // exp manquant/invalide → tente refresh
57
+
58
+ return false
59
+ }
60
+
61
+ const now = Math.floor(Date.now() / 1000)
62
+ const refreshAt = exp - this.secondesAvantExpirationTokenPourRafraichir - this.skewSeconds
63
+ if (now >= refreshAt) {
64
+ return false
65
+ }
66
+
67
+ return true
68
+ }
69
+
70
+ private async rafraichir(nomCookie: string, urlPortail: string): Promise<void> {
71
+ if (!nomCookie) return
72
+ const url = this.getRefreshUrl(urlPortail)
73
+ const controller = new AbortController()
74
+ const timeout = setTimeout(() => controller.abort(), 10_000)
75
+
76
+ try {
77
+ //Première tentative sans iframe, pour la majorité des cas.
78
+ const resp = await fetch(url, {
79
+ method: 'POST',
80
+ credentials: 'include',
81
+ headers: { Accept: 'application/json' },
82
+ redirect: 'manual',
83
+ signal: controller.signal,
84
+ })
85
+
86
+ // redirection (souvent => login) → traiter comme non auth
87
+
88
+ if (resp.type === 'opaqueredirect' || resp.status === 302) {
89
+ this.rafraichirParIframe(nomCookie, urlPortail)
90
+ return
91
+ }
92
+
93
+ // OK ou No Content: le cookie devrait être réécrit
94
+ if (resp.status === 200 || resp.status === 204) {
95
+ const jeton = this.lireCookie(nomCookie)
96
+ if (!jeton) this.rafraichirParIframe(nomCookie, urlPortail)
97
+ return
98
+ }
99
+
100
+ // non auth / expiré (401, 419) + IIS timeout (440)
101
+ if (resp.status === 401 || resp.status === 419 || resp.status === 440) {
102
+ this.rafraichirParIframe(nomCookie, urlPortail)
103
+ return
104
+ }
105
+
106
+ console.warn('Rafraichisseur token: statut inattendu', resp.status)
107
+ } catch (err: any) {
108
+ if (err?.name === 'AbortError') console.warn('RafraichisseurToken timeout')
109
+ else console.error('Erreur rafraichisseur de token', err)
110
+ // on réessaiera au prochain tick
111
+ } finally {
112
+ clearTimeout(timeout)
113
+ }
114
+ }
115
+
116
+ private rafraichirParIframe(nomCookie: string, urlPortail: string): void {
117
+ // Pour éviter les cross référence, on créé un iframe qui appel portail et force la MAJ du jeton ou l'invalidation du jeton, si jamais l'utilisateur n'est plus connecté
118
+ // ajax vers le refresh
119
+ let iframe = document.createElement('iframe')
120
+ const url = this.getRefreshUrl(urlPortail)
121
+ iframe.src = `${url}?urlRetour=${encodeURI(window.localStorage.href)}`
122
+ iframe.id = 'idRafrToken'
123
+ iframe.style.display = 'none'
124
+ document.body.appendChild(iframe)
125
+ iframe.onload = () => {
126
+ const jetonCSQC = this.lireCookie(nomCookie)
127
+ if (jetonCSQC == null || jetonCSQC === '') {
128
+ this.estDeconnecteAzure(urlPortail)
129
+ }
130
+
131
+ iframe.remove()
132
+ }
133
+ }
134
+
135
+ private estDeconnecteAzure(urlPortail: string): void {
136
+ //on envoie au portail, pas le choix
137
+
138
+ const retour = encodeURI(window.location.href)
139
+ window.open(`${urlPortail}/home/SeConnecter?urlRetour=${retour}`, '_self')
140
+ return
141
+ }
142
+
143
+ public deconnecterPortail(urlPortail: string): void {
144
+ window.location.replace(`${urlPortail}deconnecte?urlRetour=${encodeURIComponent(window.location.href)}`)
145
+ }
146
+
147
+ private lireCookie(nom: string): string | null {
148
+ if (!document.cookie) return null
149
+ const cookies = document.cookie.split(';').map(c => c.trim())
150
+ for (const cookie of cookies) {
151
+ if (cookie.startsWith(`${nom}=`)) {
152
+ try {
153
+ return decodeURIComponent(cookie.substring(nom.length + 1))
154
+ } catch {
155
+ return cookie.substring(nom.length + 1)
156
+ }
157
+ }
158
+ }
159
+ return null
160
+ }
161
+
162
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
163
+ private parseJwt(token: string): any {
164
+ const parts = token.split('.')
165
+ const base64Url = parts[1]
166
+ if (!base64Url) throw new Error('Invalid JWT format')
167
+ const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/')
168
+ const jsonPayload = decodeURIComponent(
169
+ atob(base64)
170
+ .split('')
171
+ .map(c => `%${`00${c.charCodeAt(0).toString(16)}`.slice(-2)}`)
172
+ .join(''),
173
+ )
174
+ return JSON.parse(jsonPayload)
175
+ }
176
+
177
+ // URL refresh selon env (dev = proxy Vite ; prod = portail)
178
+ private getRefreshUrl(urlPortail: string): string {
179
+ if (import.meta.env.MODE === 'development') return '/portail-refresh'
180
+ return urlPortail + '/Home/Refresh'
181
+ }
182
+ }
183
+
184
+ // Instance
185
+ const rafraichisseurToken = new RafraichisseurToken()
186
+ export default rafraichisseurToken
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codevdesign",
3
- "version": "1.0.23",
3
+ "version": "1.0.24",
4
4
  "description": "Composants Vuetify 3 pour les projets Codev",
5
5
  "files": [
6
6
  "./**/*.vue",