@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 +31 -8
- package/dist/index.d.ts +34 -0
- package/dist/index.js +52 -0
- package/dist/react.d.ts +1 -1
- package/dist/react.js +2 -2
- package/dist/server.d.ts +1 -1
- package/dist/server.js +1 -1
- package/package.json +1 -1
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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",
|