@hgdc/auth 0.4.0 → 0.6.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,13 +1,13 @@
1
1
  # @hgdc/auth
2
2
 
3
- Connexion d'un produit HGDC à HGDC Account (OIDC) : bouton « Se connecter avec HGDC », 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 H family », 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`, 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` |
9
- | `@hgdc/auth/convex` | `requireIdentity`, `requireOrgMember`, `isAuthError`, `authErrorReason`, `AUTH_ERROR_MESSAGES` |
10
- | `@hgdc/auth/react` | `HgdcSignInButton`, `startHgdcSignIn`, `HgdcMark` |
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/react` | `HgdcSignInButton`, `startHgdcSignIn`, `HgdcMark` (le H tricolore de la famille, `HGDC_MARK_SVG`) |
11
11
 
12
12
  Pairs : better-auth 1.6, convex 1.46+, React 19 (chacun facultatif selon l'import).
13
13
 
@@ -55,6 +55,31 @@ authComponent.registerRoutes(http, (ctx) => {
55
55
  })
56
56
  ```
57
57
 
58
+ ## Entrée commune `/auth/hgdc`
59
+
60
+ Le menu « Produits » (`@hgdc/ui/account-menu`) envoie vers `https://<mot>.hgdc.ch/auth/hgdc`, pas vers la page d'accueil publique. Chaque produit monte cette route serveur, au même chemin :
61
+
62
+ ```ts
63
+ // src/routes/auth/hgdc.ts
64
+ import { createHgdcEntry } from "@hgdc/auth/server"
65
+ import { createFileRoute } from "@tanstack/react-router"
66
+
67
+ import { proxyAuth } from "@/lib/auth-server" // le proxy /api/auth (createAuthProxy)
68
+
69
+ const entry = createHgdcEntry({ proxy: proxyAuth, errorCallbackURL: "/sign-in?error=1" })
70
+
71
+ export const Route = createFileRoute("/auth/hgdc")({
72
+ server: { handlers: { GET: ({ request }) => entry(request) } },
73
+ })
74
+ ```
75
+
76
+ - Session du produit ouverte : redirection vers `next`.
77
+ - Sinon la connexion HGDC part aussitôt, côté serveur (même appel que le bouton), avec retour vers `next`. Session d'Account ouverte et produit autorisé : Account renvoie sans formulaire.
78
+ - `next` (`?next=/o/acme`) n'accepte qu'un chemin du produit (`safeNextPath`) ; tout le reste devient `/`, que chaque produit redirige vers son app quand la session existe. `errorCallbackURL` : la page de connexion du produit avec son paramètre d'erreur.
79
+ - Lien vers un produit depuis ailleurs : `hgdcEntryUrl("https://mail.hgdc.ch", "/o/acme")`.
80
+
81
+ Décision : ADR 0010.
82
+
58
83
  ## Garder une fonction Convex
59
84
 
60
85
  ```ts
package/dist/index.d.ts CHANGED
@@ -6,6 +6,25 @@ export declare const HGDC_ISSUER = "https://account.hgdc.ch/api/auth";
6
6
  export declare const ORGANIZATIONS_SCOPE = "organizations";
7
7
  /** Portées demandées par défaut : identité, adresse vérifiée, renouvellement et organisations. */
8
8
  export declare const HGDC_SCOPES: readonly string[];
9
+ /**
10
+ * Entrée commune des produits : `https://<mot>.hgdc.ch/auth/hgdc?next=<chemin>` ouvre le produit
11
+ * avec la session HGDC Account, sans page d'accueil ni bouton (`createHgdcEntry`, `@hgdc/auth/server`).
12
+ * Le menu « Produits » de `@hgdc/ui` y pointe : même chemin dans chaque produit.
13
+ */
14
+ export declare const HGDC_ENTRY_PATH = "/auth/hgdc";
15
+ /** Paramètre de l'entrée qui porte la page d'arrivée. */
16
+ export declare const HGDC_ENTRY_NEXT_PARAM = "next";
17
+ /**
18
+ * Page d'arrivée sûre : un chemin du produit lui-même, jamais une autre origine. Refuse ce qu'un
19
+ * navigateur lirait comme une adresse externe (`//hôte`, `/\hôte`, schéma), les caractères de
20
+ * contrôle, et l'entrée ou `/api/auth` (boucle). Rend `fallback` dans tous ces cas.
21
+ */
22
+ export declare function safeNextPath(value: string | null | undefined, fallback?: string): string;
23
+ /**
24
+ * Adresse de l'entrée commune d'un produit : `hgdcEntryUrl("https://mail.hgdc.ch", "/o/acme")`.
25
+ * Sans `next` (ou `next` refusé), le produit ouvre sa page d'accueil connectée.
26
+ */
27
+ export declare function hgdcEntryUrl(productOrigin: string, next?: string): string;
9
28
  /** Rôles d'organisation, du plus faible au plus fort. */
10
29
  export declare const ORG_ROLES: readonly ["member", "admin", "owner"];
11
30
  export type OrgRole = (typeof ORG_ROLES)[number];
package/dist/index.js CHANGED
@@ -7,6 +7,55 @@ export const HGDC_ISSUER = "https://account.hgdc.ch/api/auth";
7
7
  export const ORGANIZATIONS_SCOPE = "organizations";
8
8
  /** Portées demandées par défaut : identité, adresse vérifiée, renouvellement et organisations. */
9
9
  export const HGDC_SCOPES = ["openid", "profile", "email", "offline_access", ORGANIZATIONS_SCOPE];
10
+ /**
11
+ * Entrée commune des produits : `https://<mot>.hgdc.ch/auth/hgdc?next=<chemin>` ouvre le produit
12
+ * avec la session HGDC Account, sans page d'accueil ni bouton (`createHgdcEntry`, `@hgdc/auth/server`).
13
+ * Le menu « Produits » de `@hgdc/ui` y pointe : même chemin dans chaque produit.
14
+ */
15
+ export const HGDC_ENTRY_PATH = "/auth/hgdc";
16
+ /** Paramètre de l'entrée qui porte la page d'arrivée. */
17
+ export const HGDC_ENTRY_NEXT_PARAM = "next";
18
+ const NEXT_MAX_LENGTH = 2048;
19
+ const NEXT_BASE = "https://next.invalid";
20
+ /**
21
+ * Page d'arrivée sûre : un chemin du produit lui-même, jamais une autre origine. Refuse ce qu'un
22
+ * navigateur lirait comme une adresse externe (`//hôte`, `/\hôte`, schéma), les caractères de
23
+ * contrôle, et l'entrée ou `/api/auth` (boucle). Rend `fallback` dans tous ces cas.
24
+ */
25
+ export function safeNextPath(value, fallback = "/") {
26
+ if (typeof value !== "string" || value.length === 0 || value.length > NEXT_MAX_LENGTH)
27
+ return fallback;
28
+ if (!value.startsWith("/") || value.startsWith("//"))
29
+ return fallback;
30
+ // Les navigateurs lisent `\` comme `/` : `/\evil.ch` serait une adresse externe.
31
+ // oxlint-disable-next-line no-control-regex -- refuser les caractères de contrôle est le but
32
+ if (/[\\\u0000-\u001f\u007f]/.test(value))
33
+ return fallback;
34
+ let url;
35
+ try {
36
+ url = new URL(value, NEXT_BASE);
37
+ }
38
+ catch {
39
+ return fallback;
40
+ }
41
+ if (url.origin !== NEXT_BASE)
42
+ return fallback;
43
+ const path = url.pathname.replace(/\/+$/, "") || "/";
44
+ if (path === HGDC_ENTRY_PATH || path === "/api/auth" || path.startsWith("/api/auth/"))
45
+ return fallback;
46
+ return `${url.pathname}${url.search}${url.hash}`;
47
+ }
48
+ /**
49
+ * Adresse de l'entrée commune d'un produit : `hgdcEntryUrl("https://mail.hgdc.ch", "/o/acme")`.
50
+ * Sans `next` (ou `next` refusé), le produit ouvre sa page d'accueil connectée.
51
+ */
52
+ export function hgdcEntryUrl(productOrigin, next) {
53
+ const url = new URL(HGDC_ENTRY_PATH, new URL(productOrigin).origin);
54
+ const path = next === undefined ? "/" : safeNextPath(next);
55
+ if (path !== "/")
56
+ url.searchParams.set(HGDC_ENTRY_NEXT_PARAM, path);
57
+ return url.toString();
58
+ }
10
59
  /** Rôles d'organisation, du plus faible au plus fort. */
11
60
  export const ORG_ROLES = ["member", "admin", "owner"];
12
61
  function isOrgRole(value) {
package/dist/react.d.ts CHANGED
@@ -35,12 +35,20 @@ export declare function startHgdcSignIn(authClient: HgdcAuthClient, { callbackUR
35
35
  ok: false;
36
36
  error: unknown;
37
37
  }>;
38
- /** Monogramme HGDC, en `currentColor`. */
39
- export declare function HgdcMark(props: {
38
+ /**
39
+ * Le H tricolore de la famille, copie exacte de `@hgdc/ui/family/hfamily-icon.svg` (un test le
40
+ * vérifie) : `@hgdc/auth` ne dépend pas de `@hgdc/ui`.
41
+ */
42
+ export declare const HGDC_MARK_SVG: string;
43
+ /**
44
+ * Le H tricolore de la famille, en image : ses dégradés gardent leurs identifiants pour eux, deux
45
+ * marques dans une page ne se marchent pas dessus. Couleurs pleines, jamais recolorées.
46
+ */
47
+ export declare function HgdcMark({ className }: {
40
48
  className?: string;
41
49
  }): ReactElement;
42
50
  /**
43
- * Bouton « Se connecter avec HGDC ». Sans style propre : le produit le met en forme par
51
+ * Bouton « Se connecter avec H family ». Sans style propre : le produit le met en forme par
44
52
  * `className`, l'état d'attente est exposé par `aria-busy` et `data-pending`.
45
53
  */
46
54
  export declare function HgdcSignInButton({ authClient, callbackURL, errorCallbackURL, onError, label, disabled, ...rest }: HgdcSignInButtonProps): ReactElement;
package/dist/react.js CHANGED
@@ -18,15 +18,23 @@ export async function startHgdcSignIn(authClient, { callbackURL = "/", errorCall
18
18
  return { ok: false, error };
19
19
  }
20
20
  }
21
- /** Monogramme HGDC, en `currentColor`. */
22
- export function HgdcMark(props) {
23
- return (_jsxs("svg", { viewBox: "0 0 24 24", width: "16", height: "16", fill: "currentColor", "aria-hidden": "true", ...props, children: [_jsx("rect", { x: "2.5", y: "8.5", width: "13", height: "7", rx: "2", transform: "rotate(45 9 12)" }), _jsx("rect", { x: "8.5", y: "8.5", width: "13", height: "7", rx: "2", transform: "rotate(45 15 12)", opacity: "0.6" })] }));
21
+ /**
22
+ * Le H tricolore de la famille, copie exacte de `@hgdc/ui/family/hfamily-icon.svg` (un test le
23
+ * vérifie) : `@hgdc/auth` ne dépend pas de `@hgdc/ui`.
24
+ */
25
+ export const HGDC_MARK_SVG = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><defs><linearGradient id="l" x1="0" y1="24" x2="0" y2="232" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#2c78fd"/><stop offset="1" stop-color="#7343fe"/></linearGradient><linearGradient id="r" x1="0" y1="24" x2="0" y2="232" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#fe5292"/><stop offset="1" stop-color="#ff9a46"/></linearGradient><linearGradient id="b" x1="64" y1="0" x2="192" y2="0" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#8145ff"/><stop offset="1" stop-color="#fe5292"/></linearGradient></defs><rect x="43" y="24" width="57" height="208" rx="18" fill="url(#l)"/><rect x="156" y="24" width="57" height="208" rx="18" fill="url(#r)"/><rect x="67" y="107" width="122" height="43" rx="14" fill="url(#b)"/></svg>';
26
+ /**
27
+ * Le H tricolore de la famille, en image : ses dégradés gardent leurs identifiants pour eux, deux
28
+ * marques dans une page ne se marchent pas dessus. Couleurs pleines, jamais recolorées.
29
+ */
30
+ export function HgdcMark({ className }) {
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 }));
24
32
  }
25
33
  /**
26
- * Bouton « Se connecter avec HGDC ». Sans style propre : le produit le met en forme par
34
+ * Bouton « Se connecter avec H family ». Sans style propre : le produit le met en forme par
27
35
  * `className`, l'état d'attente est exposé par `aria-busy` et `data-pending`.
28
36
  */
29
- export function HgdcSignInButton({ authClient, callbackURL = "/", errorCallbackURL, onError, label = "Se connecter avec HGDC", disabled, ...rest }) {
37
+ export function HgdcSignInButton({ authClient, callbackURL = "/", errorCallbackURL, onError, label = "Se connecter avec H family", disabled, ...rest }) {
30
38
  const [pending, setPending] = useState(false);
31
39
  async function signIn() {
32
40
  setPending(true);
@@ -40,5 +48,5 @@ export function HgdcSignInButton({ authClient, callbackURL = "/", errorCallbackU
40
48
  onError?.(result.error);
41
49
  }
42
50
  }
43
- return (_jsxs("button", { ...rest, type: "button", disabled: disabled === true || pending, "aria-busy": pending, "data-pending": pending ? "" : undefined, onClick: () => void signIn(), children: [_jsx(HgdcMark, {}), _jsx("span", { children: pending ? "Redirection vers HGDC…" : label })] }));
51
+ return (_jsxs("button", { ...rest, type: "button", disabled: disabled === true || pending, "aria-busy": pending, "data-pending": pending ? "" : undefined, onClick: () => void signIn(), children: [_jsx(HgdcMark, {}), _jsx("span", { children: pending ? "Redirection vers Account…" : label })] }));
44
52
  }
package/dist/server.d.ts CHANGED
@@ -19,6 +19,39 @@ export declare function restoreFetchMode(request: Request): Request;
19
19
  * `x-hgdc-fetch-mode`.
20
20
  */
21
21
  export declare function createAuthProxy(convexSiteUrl: string, fetchImpl?: typeof fetch): (request: Request) => Promise<Response>;
22
+ export type HgdcEntryOptions = {
23
+ /** Le proxy `/api/auth` du produit (`createAuthProxy`). */
24
+ proxy: (request: Request) => Promise<Response>;
25
+ /**
26
+ * Page du produit si la connexion échoue (Account refuse, réseau) : sa page de connexion avec
27
+ * son paramètre d'erreur, par exemple `/sign-in?error=1`. Chemin relatif du produit.
28
+ */
29
+ errorCallbackURL: string;
30
+ /** Chemin de better-auth, `/api/auth` par défaut. */
31
+ basePath?: string;
32
+ };
33
+ /**
34
+ * Entrée commune `GET /auth/hgdc?next=<chemin>` (`HGDC_ENTRY_PATH`), à monter comme route
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 ».
37
+ *
38
+ * - Session du produit ouverte : redirection vers `next`.
39
+ * - Sinon : la connexion HGDC part aussitôt (même appel que `HgdcSignInButton`, fait par le
40
+ * serveur), retour vers `next`. Si la session d'Account est ouverte et le produit déjà
41
+ * autorisé, Account renvoie sans formulaire : trois redirections, aucun clic.
42
+ *
43
+ * `next` est filtré par `safeNextPath` (chemin du produit seulement, pas de redirection ouverte),
44
+ * puis better-auth le revérifie contre ses `trustedOrigins`.
45
+ *
46
+ * ```ts
47
+ * // src/routes/auth/hgdc.ts
48
+ * const entry = createHgdcEntry({ proxy: proxyAuth, errorCallbackURL: "/sign-in?error=1" })
49
+ * export const Route = createFileRoute("/auth/hgdc")({
50
+ * server: { handlers: { GET: ({ request }) => entry(request) } },
51
+ * })
52
+ * ```
53
+ */
54
+ export declare function createHgdcEntry(options: HgdcEntryOptions): (request: Request) => Promise<Response>;
22
55
  export type HgdcProviderOptions = {
23
56
  /** Identifiant du client, remis par HGDC à l'enregistrement du produit. */
24
57
  clientId: string;
package/dist/server.js CHANGED
@@ -1,4 +1,4 @@
1
- import { HGDC_ISSUER, HGDC_PROVIDER_ID, HGDC_SCOPES, parseHgdcProfile } from "./index.js";
1
+ import { HGDC_ENTRY_NEXT_PARAM, HGDC_ISSUER, HGDC_PROVIDER_ID, HGDC_SCOPES, parseHgdcProfile, safeNextPath, } from "./index.js";
2
2
  /** En-tête qui porte le `sec-fetch-mode` du navigateur à travers le proxy. */
3
3
  export const FETCH_MODE_HEADER = "x-hgdc-fetch-mode";
4
4
  const FETCH_MODES = new Set(["navigate", "cors", "no-cors", "same-origin", "websocket"]);
@@ -55,6 +55,111 @@ export function createAuthProxy(convexSiteUrl, fetchImpl = fetch) {
55
55
  });
56
56
  };
57
57
  }
58
+ const STRIPPED_ON_INTERNAL_CALL = [
59
+ "content-length",
60
+ "content-type",
61
+ "accept",
62
+ "sec-fetch-mode",
63
+ "sec-fetch-dest",
64
+ "sec-fetch-site",
65
+ "sec-fetch-user",
66
+ FETCH_MODE_HEADER,
67
+ ];
68
+ function internalHeaders(request, origin) {
69
+ // Garde cookies, adresse IP et user-agent (limites de débit, sessions) ; retire le mode de
70
+ // navigation, sinon better-auth rendrait une redirection au lieu du JSON attendu.
71
+ const headers = new Headers(request.headers);
72
+ for (const name of STRIPPED_ON_INTERNAL_CALL)
73
+ headers.delete(name);
74
+ headers.set("accept", "application/json");
75
+ headers.set("origin", origin);
76
+ return headers;
77
+ }
78
+ function setCookies(response) {
79
+ return typeof response.headers.getSetCookie === "function" ? response.headers.getSetCookie() : [];
80
+ }
81
+ function redirectTo(location, cookies = []) {
82
+ const headers = new Headers({ location, "cache-control": "no-store", "referrer-policy": "no-referrer" });
83
+ for (const cookie of cookies)
84
+ headers.append("set-cookie", cookie);
85
+ return new Response(null, { status: 302, headers });
86
+ }
87
+ async function readJson(response) {
88
+ try {
89
+ return await response.json();
90
+ }
91
+ catch {
92
+ return null;
93
+ }
94
+ }
95
+ function hasSessionBody(body) {
96
+ return typeof body === "object" && body !== null && "session" in body && Boolean(body.session);
97
+ }
98
+ function authorizationUrl(body) {
99
+ if (typeof body !== "object" || body === null || !("url" in body) || typeof body.url !== "string")
100
+ return null;
101
+ try {
102
+ const url = new URL(body.url);
103
+ return url.protocol === "https:" || url.protocol === "http:" ? url.toString() : null;
104
+ }
105
+ catch {
106
+ return null;
107
+ }
108
+ }
109
+ /**
110
+ * Entrée commune `GET /auth/hgdc?next=<chemin>` (`HGDC_ENTRY_PATH`), à monter comme route
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 ».
113
+ *
114
+ * - Session du produit ouverte : redirection vers `next`.
115
+ * - Sinon : la connexion HGDC part aussitôt (même appel que `HgdcSignInButton`, fait par le
116
+ * serveur), retour vers `next`. Si la session d'Account est ouverte et le produit déjà
117
+ * autorisé, Account renvoie sans formulaire : trois redirections, aucun clic.
118
+ *
119
+ * `next` est filtré par `safeNextPath` (chemin du produit seulement, pas de redirection ouverte),
120
+ * puis better-auth le revérifie contre ses `trustedOrigins`.
121
+ *
122
+ * ```ts
123
+ * // src/routes/auth/hgdc.ts
124
+ * const entry = createHgdcEntry({ proxy: proxyAuth, errorCallbackURL: "/sign-in?error=1" })
125
+ * export const Route = createFileRoute("/auth/hgdc")({
126
+ * server: { handlers: { GET: ({ request }) => entry(request) } },
127
+ * })
128
+ * ```
129
+ */
130
+ export function createHgdcEntry(options) {
131
+ const errorCallbackURL = safeNextPath(options.errorCallbackURL, "");
132
+ if (!errorCallbackURL)
133
+ throw new Error("createHgdcEntry : errorCallbackURL doit être un chemin du produit.");
134
+ const basePath = (options.basePath ?? "/api/auth").replace(/\/+$/, "");
135
+ return async (request) => {
136
+ const url = new URL(request.url);
137
+ const next = safeNextPath(url.searchParams.get(HGDC_ENTRY_NEXT_PARAM));
138
+ const cookies = [];
139
+ try {
140
+ if (request.headers.has("cookie")) {
141
+ const session = await options.proxy(new Request(`${url.origin}${basePath}/get-session`, { headers: internalHeaders(request, url.origin) }));
142
+ cookies.push(...setCookies(session));
143
+ if (session.ok && hasSessionBody(await readJson(session)))
144
+ return redirectTo(next, cookies);
145
+ }
146
+ const headers = internalHeaders(request, url.origin);
147
+ headers.set("content-type", "application/json");
148
+ const signIn = await options.proxy(new Request(`${url.origin}${basePath}/sign-in/oauth2`, {
149
+ method: "POST",
150
+ headers,
151
+ body: JSON.stringify({ providerId: HGDC_PROVIDER_ID, callbackURL: next, errorCallbackURL }),
152
+ }));
153
+ const target = signIn.ok ? authorizationUrl(await readJson(signIn)) : null;
154
+ if (!target)
155
+ return redirectTo(errorCallbackURL, cookies);
156
+ return redirectTo(target, [...cookies, ...setCookies(signIn)]);
157
+ }
158
+ catch {
159
+ return redirectTo(errorCallbackURL, cookies);
160
+ }
161
+ };
162
+ }
58
163
  /**
59
164
  * Configuration du fournisseur « HGDC » pour le plugin `genericOAuth` de better-auth :
60
165
  * découverte OIDC, code d'autorisation avec PKCE, émetteur vérifié contre la confusion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hgdc/auth",
3
- "version": "0.4.0",
3
+ "version": "0.6.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",