@hgdc/auth 0.9.0 → 0.11.0

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/README.md CHANGED
@@ -1,14 +1,14 @@
1
1
  # @hgdc/auth
2
2
 
3
- Connexion d'un produit de la famille à Account (OIDC) : bouton « Se connecter avec H family », configuration du client better-auth, proxy `/api/auth` pour TanStack Start et gardes Convex.
3
+ Connexion d'un produit de la famille à Account (OIDC) : bouton « Se connecter avec HGDC », configuration du client better-auth, proxy `/api/auth` pour TanStack Start et gardes Convex.
4
4
 
5
- | Import | Contenu |
6
- | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
7
- | `@hgdc/auth` | `HGDC_ISSUER`, `HGDC_PROVIDER_ID`, `HGDC_SCOPES`, entrée commune (`HGDC_ENTRY_PATH`, `safeNextPath`, `hgdcEntryUrl`), rôles (`highestRole`, `hasRole`), `parseHgdcProfile`, `verifiedOrganizationsFromIdToken`, `verifyHgdcIdToken`, `planOrganizationSync`, fraîcheur (`membershipIsFresh`, `SESSION_TTL_SECONDS`, `MEMBERSHIP_TTL_MS`, `KEY_MEMBERSHIP_TTL_MS`) |
8
- | `@hgdc/auth/server` | `hgdcProvider` (plugin `genericOAuth`), `createAuthProxy`, `restoreFetchMode`, `createHgdcEntry` |
9
- | `@hgdc/auth/convex` | `requireIdentity`, `requireOrgMember`, `isAuthError`, `authErrorReason`, `AUTH_ERROR_MESSAGES` |
10
- | `@hgdc/auth/events` | Événements poussés par Account : `ACCOUNT_EVENTS_PATH`, `signAccountEvent`, `verifyAccountEvent`, `handleAccountEventRequest`, `accountEventSchema`, `accountEventIsStale`, `ACCOUNT_EVENT_RETENTION_MS`, et pour Convex `vAccountEvent`, `accountEventsTable` |
11
- | `@hgdc/auth/react` | `HgdcSignInButton`, `startHgdcSignIn`, `HgdcMark` (le H tricolore de la famille, `HGDC_MARK_SVG`) |
5
+ | Import | Contenu |
6
+ | ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
7
+ | `@hgdc/auth` | `HGDC_ISSUER`, `HGDC_PROVIDER_ID`, `HGDC_SCOPES`, entrée commune (`HGDC_ENTRY_PATH`, `safeNextPath`, `hgdcEntryUrl`), rôles (`highestRole`, `hasRole`), `parseHgdcProfile`, `verifiedOrganizationsFromIdToken`, `verifyHgdcIdToken`, `planOrganizationSync`, fraîcheur (`membershipIsFresh`, `SESSION_TTL_SECONDS`, `MEMBERSHIP_TTL_MS`, `KEY_MEMBERSHIP_TTL_MS`), `createSessionTokenCache` |
8
+ | `@hgdc/auth/server` | `hgdcProvider` (plugin `genericOAuth`), `createAuthProxy`, `restoreFetchMode`, `createHgdcEntry` |
9
+ | `@hgdc/auth/convex` | `requireIdentity`, `requireOrgMember`, `isAuthError`, `authErrorReason`, `AUTH_ERROR_MESSAGES` |
10
+ | `@hgdc/auth/events` | Événements poussés par Account : `ACCOUNT_EVENTS_PATH`, `signAccountEvent`, `verifyAccountEvent`, `handleAccountEventRequest`, `accountEventSchema`, `accountEventIsStale`, `ACCOUNT_EVENT_RETENTION_MS`, et pour Convex `vAccountEvent`, `accountEventsTable` |
11
+ | `@hgdc/auth/react` | `HgdcSignInButton`, `startHgdcSignIn`, `HgdcMark` (le H tricolore de la famille, `HGDC_MARK_SVG`) |
12
12
 
13
13
  Pairs : better-auth 1.6, convex 1.46+, React 19 (chacun facultatif selon l'import).
14
14
 
@@ -131,6 +131,29 @@ if (!organization && !hasSyncedMarker(location.searchStr))
131
131
 
132
132
  Décisions : ADR 0010, ADR 0013.
133
133
 
134
+ ## Session dans le `beforeLoad` de la racine
135
+
136
+ Sans cache, chaque navigation côté navigateur rappelait le serveur (`getAuth`) avant tout rendu : l'ancienne page restait figée, et TanStack Router n'affiche aucun chargement pendant un `beforeLoad`. `createSessionTokenCache` reprend au navigateur une session ouverte pendant 5 minutes (le client Convex renouvelle lui-même son jeton), relit toujours au serveur, et s'amorce avec le jeton du rendu serveur :
137
+
138
+ ```ts
139
+ const session = createSessionTokenCache(() => getAuth())
140
+
141
+ export const Route = createRootRouteWithContext<…>()({
142
+ beforeLoad: async ({ context }) => {
143
+ const token = await session.read()
144
+ if (token) context.convexQueryClient.serverHttpClient?.setAuth(token)
145
+ return { isAuthenticated: Boolean(token), token }
146
+ },
147
+ component: () => {
148
+ const context = Route.useRouteContext()
149
+ useEffect(() => session.seed(context.token), [context.token])
150
+ …
151
+ },
152
+ })
153
+ ```
154
+
155
+ Une absence de session n'est jamais gardée. Déconnexion : recharger la page, ou `session.clear()`.
156
+
134
157
  ## Garder une fonction Convex
135
158
 
136
159
  ```ts
package/dist/index.d.ts CHANGED
@@ -190,3 +190,37 @@ export declare const KEY_MEMBERSHIP_TTL_MS: number;
190
190
  export declare function membershipIsFresh(membership: {
191
191
  syncedAt?: number | null | undefined;
192
192
  }, now: number, ttl?: number): boolean;
193
+ /**
194
+ * Durée pendant laquelle le navigateur reprend le jeton de session déjà lu plutôt que de le
195
+ * redemander au serveur : 5 minutes. Le client Convex renouvelle lui-même son jeton ; celui-ci ne
196
+ * sert qu'aux gardes de route et à amorcer le client.
197
+ */
198
+ export declare const CLIENT_SESSION_TOKEN_TTL_MS: number;
199
+ export type SessionTokenCache = {
200
+ /** Jeton courant : au serveur, toujours relu ; au navigateur, repris s'il a moins de 5 minutes. */
201
+ read: () => Promise<string | null>;
202
+ /** Amorce le cache avec le jeton du rendu serveur, à l'hydratation (la racine ne repasse pas par `read`). */
203
+ seed: (token: string | null | undefined) => void;
204
+ /** Oublie le jeton (déconnexion sans rechargement). */
205
+ clear: () => void;
206
+ };
207
+ /**
208
+ * Jeton de session pour le `beforeLoad` de la racine d'un produit TanStack Start. Sans cache, chaque
209
+ * navigation côté navigateur rappelait le serveur (`getAuth`) avant tout rendu : l'ancienne page
210
+ * restait figée le temps de l'aller-retour. Seule une session ouverte est gardée, jamais une absence
211
+ * de session ; les appels simultanés partagent la même requête.
212
+ *
213
+ * ```ts
214
+ * const session = createSessionTokenCache(() => getAuth())
215
+ * beforeLoad: async ({ context }) => {
216
+ * const token = await session.read()
217
+ * …
218
+ * }
219
+ * // composant racine : useEffect(() => session.seed(context.token), [context.token])
220
+ * ```
221
+ */
222
+ export declare function createSessionTokenCache(fetchToken: () => Promise<string | null>, options?: {
223
+ ttlMs?: number;
224
+ now?: () => number;
225
+ isBrowser?: () => boolean;
226
+ }): SessionTokenCache;
package/dist/index.js CHANGED
@@ -313,3 +313,55 @@ export function membershipIsFresh(membership, now, ttl = MEMBERSHIP_TTL_MS) {
313
313
  const { syncedAt } = membership;
314
314
  return typeof syncedAt === "number" && now - syncedAt <= ttl;
315
315
  }
316
+ /**
317
+ * Durée pendant laquelle le navigateur reprend le jeton de session déjà lu plutôt que de le
318
+ * redemander au serveur : 5 minutes. Le client Convex renouvelle lui-même son jeton ; celui-ci ne
319
+ * sert qu'aux gardes de route et à amorcer le client.
320
+ */
321
+ export const CLIENT_SESSION_TOKEN_TTL_MS = 5 * 60 * 1000;
322
+ /**
323
+ * Jeton de session pour le `beforeLoad` de la racine d'un produit TanStack Start. Sans cache, chaque
324
+ * navigation côté navigateur rappelait le serveur (`getAuth`) avant tout rendu : l'ancienne page
325
+ * restait figée le temps de l'aller-retour. Seule une session ouverte est gardée, jamais une absence
326
+ * de session ; les appels simultanés partagent la même requête.
327
+ *
328
+ * ```ts
329
+ * const session = createSessionTokenCache(() => getAuth())
330
+ * beforeLoad: async ({ context }) => {
331
+ * const token = await session.read()
332
+ * …
333
+ * }
334
+ * // composant racine : useEffect(() => session.seed(context.token), [context.token])
335
+ * ```
336
+ */
337
+ export function createSessionTokenCache(fetchToken, options = {}) {
338
+ const ttl = options.ttlMs ?? CLIENT_SESSION_TOKEN_TTL_MS;
339
+ const now = options.now ?? Date.now;
340
+ const isBrowser = options.isBrowser ?? (() => typeof document !== "undefined");
341
+ let cached = null;
342
+ let inflight = null;
343
+ return {
344
+ read: async () => {
345
+ if (!isBrowser())
346
+ return await fetchToken();
347
+ if (cached && now() - cached.at < ttl)
348
+ return cached.token;
349
+ inflight ??= fetchToken()
350
+ .then((token) => {
351
+ cached = token ? { token, at: now() } : null;
352
+ return token;
353
+ })
354
+ .finally(() => {
355
+ inflight = null;
356
+ });
357
+ return await inflight;
358
+ },
359
+ seed: (token) => {
360
+ if (isBrowser() && token && !cached)
361
+ cached = { token, at: now() };
362
+ },
363
+ clear: () => {
364
+ cached = null;
365
+ },
366
+ };
367
+ }
package/dist/react.d.ts CHANGED
@@ -48,7 +48,7 @@ export declare function HgdcMark({ className }: {
48
48
  className?: string;
49
49
  }): ReactElement;
50
50
  /**
51
- * Bouton « Se connecter avec H family ». Sans style propre : le produit le met en forme par
51
+ * Bouton « Se connecter avec HGDC ». Sans style propre : le produit le met en forme par
52
52
  * `className`, l'état d'attente est exposé par `aria-busy` et `data-pending`.
53
53
  */
54
54
  export declare function HgdcSignInButton({ authClient, callbackURL, errorCallbackURL, onError, label, disabled, ...rest }: HgdcSignInButtonProps): ReactElement;
package/dist/react.js CHANGED
@@ -31,10 +31,10 @@ export function HgdcMark({ className }) {
31
31
  return (_jsx("img", { src: `data:image/svg+xml,${encodeURIComponent(HGDC_MARK_SVG)}`, alt: "", "aria-hidden": "true", width: 16, height: 16, draggable: false, className: className }));
32
32
  }
33
33
  /**
34
- * Bouton « Se connecter avec H family ». Sans style propre : le produit le met en forme par
34
+ * Bouton « Se connecter avec HGDC ». Sans style propre : le produit le met en forme par
35
35
  * `className`, l'état d'attente est exposé par `aria-busy` et `data-pending`.
36
36
  */
37
- export function HgdcSignInButton({ authClient, callbackURL = "/", errorCallbackURL, onError, label = "Se connecter avec H family", disabled, ...rest }) {
37
+ export function HgdcSignInButton({ authClient, callbackURL = "/", errorCallbackURL, onError, label = "Se connecter avec HGDC", disabled, ...rest }) {
38
38
  const [pending, setPending] = useState(false);
39
39
  async function signIn() {
40
40
  setPending(true);
package/dist/server.d.ts CHANGED
@@ -33,7 +33,7 @@ export type HgdcEntryOptions = {
33
33
  /**
34
34
  * Entrée commune `GET /auth/hgdc?next=<chemin>` (`HGDC_ENTRY_PATH`), à monter comme route
35
35
  * serveur du produit. Le menu « Produits » de `@hgdc/ui` y envoie : on entre dans le produit
36
- * sans passer par sa page d'accueil ni recliquer « Se connecter avec H family ».
36
+ * sans passer par sa page d'accueil ni recliquer « Se connecter avec HGDC ».
37
37
  *
38
38
  * - Session du produit ouverte : redirection vers `next`.
39
39
  * - Sinon : la connexion HGDC part aussitôt (même appel que `HgdcSignInButton`, fait par le
package/dist/server.js CHANGED
@@ -109,7 +109,7 @@ function authorizationUrl(body) {
109
109
  /**
110
110
  * Entrée commune `GET /auth/hgdc?next=<chemin>` (`HGDC_ENTRY_PATH`), à monter comme route
111
111
  * serveur du produit. Le menu « Produits » de `@hgdc/ui` y envoie : on entre dans le produit
112
- * sans passer par sa page d'accueil ni recliquer « Se connecter avec H family ».
112
+ * sans passer par sa page d'accueil ni recliquer « Se connecter avec HGDC ».
113
113
  *
114
114
  * - Session du produit ouverte : redirection vers `next`.
115
115
  * - Sinon : la connexion HGDC part aussitôt (même appel que `HgdcSignInButton`, fait par le
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hgdc/auth",
3
- "version": "0.9.0",
3
+ "version": "0.11.0",
4
4
  "description": "Connexion HGDC : bouton « Se connecter avec HGDC », config better-auth et gardes Convex.",
5
5
  "license": "UNLICENSED",
6
6
  "homepage": "https://github.com/hugogebel-boop/hgdc-kit/tree/main/packages/auth#readme",