@imtf/auth 1.1.0-beta.2 → 2.0.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 ADDED
@@ -0,0 +1,167 @@
1
+ # @imtf/auth - Better Auth wrapper
2
+
3
+ Thin wrapper around [`better-auth`](https://www.better-auth.com/) tailored for IMTF applications. Provides IMTF-specific auth configuration, hooks, and route helpers on top of `better-auth`, for use in Next.js apps.
4
+
5
+ ## Pre-requisites
6
+
7
+ The following peer dependencies must be installed:
8
+
9
+ - `better-auth` 1.7.1
10
+ - `zod` 4.3.6 or higher — must stay version-matched with `better-auth`'s own `zod` dependency
11
+ - `@mui/material` 7.1.0 or higher
12
+ - `@mui/lab` 7.0.1-beta.18 or higher
13
+ - `react` and `react-dom` 18.0.0 or 19.0.0 or higher
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ npm install @imtf/auth
19
+ ```
20
+
21
+ ## Usage
22
+
23
+ This package supplies IMTF's `better-auth` configuration and a couple of OIDC-flow helpers — it does not wrap `betterAuth()`/`createAuthClient()` themselves. Call those directly with the options this package returns.
24
+
25
+ ### Server: `auth.ts`
26
+
27
+ ```ts
28
+ import "server-only";
29
+
30
+ import { getBetterAuthOptions } from "@imtf/auth";
31
+ import { betterAuth, type BetterAuthOptions } from "better-auth";
32
+
33
+ export const auth = betterAuth({
34
+ ...getBetterAuthOptions({
35
+ oidcDiscoveryUrl: `${process.env.OIDC_ISSUER_URL}/.well-known/openid-configuration`,
36
+ authSecret: process.env.AUTH_SECRET!,
37
+ authClientId: process.env.OIDC_CLIENT_ID!,
38
+ authClientSecret: process.env.OIDC_CLIENT_SECRET!,
39
+ authBaseURL: process.env.AUTH_BASE_URL,
40
+ }),
41
+ } satisfies BetterAuthOptions);
42
+ ```
43
+
44
+ ### Client: `auth-client.ts`
45
+
46
+ ```ts
47
+ "use client";
48
+
49
+ import { createAuthClient } from "better-auth/react";
50
+
51
+ export const authClient = createAuthClient();
52
+ ```
53
+
54
+ ### Route handler: `app/api/auth/[...all]/route.ts`
55
+
56
+ ```ts
57
+ import type { NextRequest } from "next/server";
58
+ import { auth } from "@/auth";
59
+
60
+ function handler(request: NextRequest) {
61
+ return auth.handler(request);
62
+ }
63
+
64
+ export const GET = handler;
65
+ export const POST = handler;
66
+ ```
67
+
68
+ ### Federated sign-out route: `app/api/auth/sign-out-url/route.ts`
69
+
70
+ ```ts
71
+ import { getSignOutUrlRoute } from "@imtf/auth";
72
+ import { headers } from "next/headers";
73
+ import { auth } from "@/auth";
74
+
75
+ export const GET = getSignOutUrlRoute({
76
+ auth,
77
+ discoveryUrl: `${process.env.OIDC_ISSUER_URL}/.well-known/openid-configuration`,
78
+ headers,
79
+ clientId: process.env.OIDC_CLIENT_ID,
80
+ });
81
+ ```
82
+
83
+ > `clientId` lets the route build an end-session URL even when no ID token is available — for example when the OIDC provider's callback failed before better-auth stored an account cookie (missing email claim, denied consent, etc.). Without it, that case has no way to end the provider's SSO session, which otherwise leaves the user stuck in a silent re-authentication loop.
84
+
85
+ ### Session check in a server component
86
+
87
+ ```ts
88
+ import { headers } from "next/headers";
89
+ import { redirect } from "next/navigation";
90
+ import { auth } from "@/auth";
91
+
92
+ export async function AuthGuard({ children }: { children: React.ReactNode }) {
93
+ const { accessToken } = await auth.api
94
+ .getAccessToken({ body: { useAccountCookie: true }, headers: await headers() })
95
+ .catch(() => ({ accessToken: undefined }));
96
+
97
+ if (!accessToken) redirect("/auth/sign-in");
98
+
99
+ return children;
100
+ }
101
+ ```
102
+
103
+ ### Sign-in and sign-out (client components)
104
+
105
+ ```tsx
106
+ "use client";
107
+
108
+ import { PROVIDER_ID, useSignOut } from "@imtf/auth";
109
+ import { authClient } from "@/auth-client";
110
+
111
+ function SignInButton() {
112
+ const handleSignIn = () =>
113
+ void authClient.signIn.social({ provider: PROVIDER_ID, callbackURL: "/" });
114
+
115
+ return <button onClick={handleSignIn}>Sign in</button>;
116
+ }
117
+
118
+ function SignOutButton() {
119
+ const { handleSignOut } = useSignOut({ authClient });
120
+
121
+ return <button onClick={handleSignOut}>Sign out</button>;
122
+ }
123
+ ```
124
+
125
+ ### Full export list
126
+
127
+ ```ts
128
+ import {
129
+ getBetterAuthOptions,
130
+ getBetterAuthClientOptions,
131
+ getSignOutUrlRoute,
132
+ useSignOut,
133
+ PROVIDER_ID,
134
+ } from "@imtf/auth";
135
+ ```
136
+
137
+ > `getBetterAuthClientOptions` is deprecated as of `better-auth` 1.7 — generic OAuth sign-in no longer needs a client plugin, so `createAuthClient()` can be called with no options as shown above.
138
+
139
+ ## Development
140
+
141
+ Prior to development, make sure you have installed the dependencies from the **repository root**:
142
+
143
+ ```bash
144
+ pnpm install
145
+ ```
146
+
147
+ ## Testing
148
+
149
+ To run the tests, just run:
150
+
151
+ ```bash
152
+ pnpm run test
153
+ ```
154
+
155
+ Coverage report can be generated with:
156
+
157
+ ```bash
158
+ pnpm run test:coverage
159
+ ```
160
+
161
+ ## Migration from v1 to v2
162
+
163
+ `@imtf/auth` v2 upgrades its `better-auth` peer dependency from `1.6.30` to `1.7.1`. `better-auth` 1.7 rewrote the generic OAuth plugin as a first-class social provider — see the [Better Auth 1.7 upgrade guide](https://www.better-auth.com/docs/guides/1-7-upgrade-guide) for the full list of breaking changes upstream. The changes relevant to consumers of this package are:
164
+
165
+ - **OAuth callback URL path changed.** The redirect URI that `better-auth` exposes for the generic OIDC provider moved from `{baseURL}/oauth2/callback/{providerId}` to `{baseURL}/callback/{providerId}` (e.g. `https://app.example.com/api/auth/oauth2/callback/oidc-provider` → `https://app.example.com/api/auth/callback/oidc-provider`). **You must update the redirect URI registered with your OIDC provider/IdP client** before deploying this upgrade, or sign-in will fail.
166
+ - **`getSignOutUrlRoute` dropped its `providerId` option.** `better-auth`'s `getAccessToken` API no longer accepts `providerId` to select an account; it now resolves the account from the signed account cookie (`useAccountCookie: true`), matching this package's database-less, `storeAccountCookie: true` configuration. If you were passing `providerId` to `getSignOutUrlRoute`, remove it — it is no longer read.
167
+ - **No client plugin is required for generic OAuth sign-in anymore.** `getBetterAuthClientOptions()` no longer registers `genericOAuthClient()` (removed upstream). Generic/custom OAuth providers now sign in through the standard `authClient.signIn.social({ provider })` / `authClient.linkSocial({ provider })` methods. No action needed on your end beyond upgrading — this package's exported options already reflect the change.
@@ -31,12 +31,14 @@ function useSignOut({ postSignOutRedirectPath = "/auth/sign-in?sign-out=success"
31
31
  /**
32
32
  * Create a logout URL before we invoke authClient.signOut(), since it clears critical tokens.
33
33
  */
34
- const response = await fetch(`${retrieveSignOutUrlPath}?post_logout_redirect_uri=${encodeURIComponent(postLogoutRedirectUri)}`);
34
+ const response = await fetch(`${origin}${retrieveSignOutUrlPath}?post_logout_redirect_uri=${encodeURIComponent(postLogoutRedirectUri)}`);
35
35
  if (!response.ok) {
36
- const { error } = await response.json().catch(() => ({ error: null }));
36
+ const errorBody = await response.json().catch(() => null);
37
+ const error = errorBody !== null && typeof errorBody === "object" && "error" in errorBody && typeof errorBody.error === "string" ? errorBody.error : null;
37
38
  throw new Error(error ?? "Failed to create logout URL");
38
39
  }
39
- const { url } = await response.json();
40
+ const responseBody = await response.json();
41
+ const url = responseBody !== null && typeof responseBody === "object" && "url" in responseBody && typeof responseBody.url === "string" ? responseBody.url : null;
40
42
  if (!url) {
41
43
  standardSignOut();
42
44
  return;
@@ -1,8 +1,15 @@
1
1
  "use client";
2
- import { genericOAuthClient } from "better-auth/client/plugins";
3
2
  //#region src/options/auth-client.ts
3
+ /**
4
+ * Generic OAuth providers sign in through the standard `signIn.social` /
5
+ * `linkSocial` client methods (better-auth 1.7+), so no client plugin is
6
+ * required.
7
+ *
8
+ * @deprecated No longer needed — pass options directly to `createAuthClient`
9
+ * instead. Will be removed in a future major version.
10
+ */
4
11
  function getBetterAuthClientOptions() {
5
- return { plugins: [genericOAuthClient()] };
12
+ return {};
6
13
  }
7
14
  //#endregion
8
15
  export { getBetterAuthClientOptions };
@@ -14,11 +14,11 @@ function getBetterAuthOptions({ oidcDiscoveryUrl, authSecret, authBaseURL, authC
14
14
  cookieCache: {
15
15
  enabled: true,
16
16
  version: "1",
17
- maxAge: 3600 * 24,
17
+ maxAge: 86400,
18
18
  strategy: "jwe",
19
19
  refreshCache: true
20
20
  },
21
- expiresIn: 3600 * 24,
21
+ expiresIn: 86400,
22
22
  updateAge: 3600,
23
23
  freshAge: 300
24
24
  },
@@ -1,6 +1,5 @@
1
- import { PROVIDER_ID } from "../constants.esm.js";
2
1
  //#region src/routes/getSignOutUrlRoute.ts
3
- function getSignOutUrlRoute({ auth, discoveryUrl, federatedLogout = true, providerId = PROVIDER_ID, headers }) {
2
+ function getSignOutUrlRoute({ auth, discoveryUrl, federatedLogout = true, headers, clientId }) {
4
3
  /**
5
4
  * API route that constructs a federated logout URL for OIDC providers.
6
5
  * Returns null if federated logout is disabled.
@@ -13,30 +12,45 @@ function getSignOutUrlRoute({ auth, discoveryUrl, federatedLogout = true, provid
13
12
  if (!federatedLogout || !discoveryUrl) return Response.json({ url: null }, { status: 200 });
14
13
  /**
15
14
  * Retrieve the user's ID token, to produce a valid logout URL.
15
+ * `useAccountCookie` reads the single OAuth account stored in the
16
+ * signed cookie (database-less flow), matching `storeAccountCookie`.
16
17
  */
17
18
  const token = await auth.api.getAccessToken({
18
- body: { providerId },
19
+ body: { useAccountCookie: true },
19
20
  headers: await headers()
20
21
  }).catch(() => null);
21
- if (!token?.idToken) return Response.json({ error: "No ID token found for user" }, { status: 400 });
22
+ /**
23
+ * No ID token is available, e.g. when the OIDC provider redirected back
24
+ * with an error (missing email, denied consent, etc.) before better-auth
25
+ * ever stored an account cookie. Without `client_id`, we have no way to
26
+ * end the provider's SSO session, which would otherwise leave the user
27
+ * stuck in a silent re-authentication loop with no way to recover.
28
+ */
29
+ if (!token?.idToken && !clientId) return Response.json({ error: "No ID token found for user" }, { status: 400 });
22
30
  /**
23
31
  * Fetch the well-known configuration from the OIDC provider to construct the
24
32
  * logout URL.
25
33
  */
26
34
  const wellKnownResponse = await fetch(discoveryUrl);
27
35
  if (!wellKnownResponse.ok) return Response.json({ error: "Failed to fetch OIDC discovery document" }, { status: 500 });
28
- const endSessionEndpoint = (await wellKnownResponse.json()).end_session_endpoint;
36
+ const wellKnownConfigJson = await wellKnownResponse.json();
37
+ const endSessionEndpoint = wellKnownConfigJson !== null && typeof wellKnownConfigJson === "object" && "end_session_endpoint" in wellKnownConfigJson && typeof wellKnownConfigJson.end_session_endpoint === "string" ? wellKnownConfigJson.end_session_endpoint : void 0;
29
38
  if (!endSessionEndpoint) return Response.json({ error: "Could not determine end_session_endpoint" }, { status: 500 });
30
39
  /**
31
40
  * Construct the logout URL with parameters.
32
41
  * - end_session_endpoint: The OIDC provider's end_session_endpoint.
33
42
  * - post_logout_redirect_uri: The URL to redirect to after logout.
34
- * - id_token_hint: The user's ID token.
43
+ * - id_token_hint: The user's ID token, when available.
44
+ * - client_id: Fallback identity hint when there's no ID token. The
45
+ * provider uses its own browser session cookie to find the SSO
46
+ * session to end, so this still works without a token, though it may
47
+ * show a logout confirmation page instead of ending silently.
35
48
  */
36
49
  const logoutUrl = new URL(endSessionEndpoint);
37
50
  const postLogoutRedirectUri = request.nextUrl.searchParams.get("post_logout_redirect_uri") ?? "/auth/sign-in";
38
51
  logoutUrl.searchParams.set("post_logout_redirect_uri", postLogoutRedirectUri);
39
- logoutUrl.searchParams.set("id_token_hint", token.idToken);
52
+ if (token?.idToken) logoutUrl.searchParams.set("id_token_hint", token.idToken);
53
+ else if (clientId) logoutUrl.searchParams.set("client_id", clientId);
40
54
  return Response.json({ url: logoutUrl.toString() });
41
55
  } catch (error) {
42
56
  console.error("Failed to construct logout URL:", error);
@@ -21,7 +21,11 @@ export type UseSignOutOptions = {
21
21
  */
22
22
  authClient: ReturnType<typeof createAuthClient>;
23
23
  /**
24
- * Optional origin to use for constructing the post-logout redirect URI. Defaults to the current window location origin.
24
+ * Optional origin (and, if applicable, base path) prepended to both
25
+ * `retrieveSignOutUrlPath` and `postSignOutRedirectPath`. Defaults to the
26
+ * current window location origin. Useful when the app is served under a
27
+ * base path, since these URLs are built outside Next's router and don't
28
+ * get it auto-applied.
25
29
  */
26
30
  origin?: string;
27
31
  };
@@ -33,6 +37,6 @@ export type UseSignOutOptions = {
33
37
  * const { handleSignOut } = useSignOut({ postSignOutRedirectPath: "/auth/sign-in?sign-out=success" });
34
38
  * ```
35
39
  */
36
- export declare function useSignOut({ postSignOutRedirectPath, retrieveSignOutUrlPath, onSignOutStart, onSignOutSuccess, authClient, origin: providedOrigin }: UseSignOutOptions): {
40
+ export declare function useSignOut({ postSignOutRedirectPath, retrieveSignOutUrlPath, onSignOutStart, onSignOutSuccess, authClient, origin: providedOrigin, }: UseSignOutOptions): {
37
41
  handleSignOut: () => Promise<void>;
38
42
  };
@@ -1,178 +1,9 @@
1
- export declare function getBetterAuthClientOptions(): {
2
- readonly plugins: [{
3
- id: "generic-oauth-client";
4
- version: string;
5
- $InferServerPlugin: ReturnType<(options: import("better-auth/plugins").GenericOAuthOptions) => {
6
- id: "generic-oauth";
7
- version: string;
8
- init: (ctx: import("better-auth").AuthContext) => {
9
- context: {
10
- socialProviders: import("better-auth").OAuthProvider<Record<string, any>, Partial<import("better-auth").ProviderOptions<any>>>[];
11
- };
12
- };
13
- endpoints: {
14
- signInWithOAuth2: import("better-call").StrictEndpoint<"/sign-in/oauth2", {
15
- method: "POST";
16
- body: import("zod").ZodObject<{
17
- providerId: import("zod").ZodString;
18
- callbackURL: import("zod").ZodOptional<import("zod").ZodString>;
19
- errorCallbackURL: import("zod").ZodOptional<import("zod").ZodString>;
20
- newUserCallbackURL: import("zod").ZodOptional<import("zod").ZodString>;
21
- disableRedirect: import("zod").ZodOptional<import("zod").ZodBoolean>;
22
- scopes: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString>>;
23
- requestSignUp: import("zod").ZodOptional<import("zod").ZodBoolean>;
24
- additionalData: import("zod").ZodOptional<import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodAny>>;
25
- }, import("zod/v4/core").$strip>;
26
- metadata: {
27
- openapi: {
28
- description: string;
29
- responses: {
30
- 200: {
31
- description: string;
32
- content: {
33
- "application/json": {
34
- schema: {
35
- type: "object";
36
- properties: {
37
- url: {
38
- type: string;
39
- };
40
- redirect: {
41
- type: string;
42
- };
43
- };
44
- };
45
- };
46
- };
47
- };
48
- };
49
- };
50
- };
51
- }, {
52
- url: string;
53
- redirect: boolean;
54
- }>;
55
- oAuth2Callback: import("better-call").StrictEndpoint<"/oauth2/callback/:providerId", {
56
- method: "GET";
57
- query: import("zod").ZodObject<{
58
- code: import("zod").ZodOptional<import("zod").ZodString>;
59
- error: import("zod").ZodOptional<import("zod").ZodString>;
60
- error_description: import("zod").ZodOptional<import("zod").ZodString>;
61
- state: import("zod").ZodOptional<import("zod").ZodString>;
62
- iss: import("zod").ZodOptional<import("zod").ZodString>;
63
- }, import("zod/v4/core").$strip>;
64
- metadata: {
65
- allowedMediaTypes: string[];
66
- openapi: {
67
- description: string;
68
- responses: {
69
- 200: {
70
- description: string;
71
- content: {
72
- "application/json": {
73
- schema: {
74
- type: "object";
75
- properties: {
76
- url: {
77
- type: string;
78
- };
79
- };
80
- };
81
- };
82
- };
83
- };
84
- };
85
- };
86
- scope: "server";
87
- };
88
- }, void>;
89
- oAuth2LinkAccount: import("better-call").StrictEndpoint<"/oauth2/link", {
90
- method: "POST";
91
- body: import("zod").ZodObject<{
92
- providerId: import("zod").ZodString;
93
- callbackURL: import("zod").ZodString;
94
- scopes: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString>>;
95
- errorCallbackURL: import("zod").ZodOptional<import("zod").ZodString>;
96
- }, import("zod/v4/core").$strip>;
97
- use: ((inputContext: import("better-call").MiddlewareInputContext<import("better-call").MiddlewareOptions>) => Promise<{
98
- session: {
99
- session: Record<string, any> & {
100
- id: string;
101
- createdAt: Date;
102
- updatedAt: Date;
103
- userId: string;
104
- expiresAt: Date;
105
- token: string;
106
- ipAddress?: string | null | undefined;
107
- userAgent?: string | null | undefined;
108
- };
109
- user: Record<string, any> & {
110
- id: string;
111
- createdAt: Date;
112
- updatedAt: Date;
113
- email: string;
114
- emailVerified: boolean;
115
- name: string;
116
- image?: string | null | undefined;
117
- };
118
- };
119
- }>)[];
120
- metadata: {
121
- openapi: {
122
- description: string;
123
- responses: {
124
- "200": {
125
- description: string;
126
- content: {
127
- "application/json": {
128
- schema: {
129
- type: "object";
130
- properties: {
131
- url: {
132
- type: string;
133
- format: string;
134
- description: string;
135
- };
136
- redirect: {
137
- type: string;
138
- description: string;
139
- enum: boolean[];
140
- };
141
- };
142
- required: string[];
143
- };
144
- };
145
- };
146
- };
147
- };
148
- };
149
- };
150
- }, {
151
- url: string;
152
- redirect: boolean;
153
- }>;
154
- };
155
- options: import("better-auth/plugins").GenericOAuthOptions;
156
- $ERROR_CODES: {
157
- INVALID_OAUTH_CONFIGURATION: import("better-auth").RawError<"INVALID_OAUTH_CONFIGURATION">;
158
- TOKEN_URL_NOT_FOUND: import("better-auth").RawError<"TOKEN_URL_NOT_FOUND">;
159
- PROVIDER_CONFIG_NOT_FOUND: import("better-auth").RawError<"PROVIDER_CONFIG_NOT_FOUND">;
160
- PROVIDER_ID_REQUIRED: import("better-auth").RawError<"PROVIDER_ID_REQUIRED">;
161
- INVALID_OAUTH_CONFIG: import("better-auth").RawError<"INVALID_OAUTH_CONFIG">;
162
- SESSION_REQUIRED: import("better-auth").RawError<"SESSION_REQUIRED">;
163
- ISSUER_MISMATCH: import("better-auth").RawError<"ISSUER_MISMATCH">;
164
- ISSUER_MISSING: import("better-auth").RawError<"ISSUER_MISSING">;
165
- };
166
- }>;
167
- $ERROR_CODES: {
168
- INVALID_OAUTH_CONFIGURATION: import("better-auth").RawError<"INVALID_OAUTH_CONFIGURATION">;
169
- TOKEN_URL_NOT_FOUND: import("better-auth").RawError<"TOKEN_URL_NOT_FOUND">;
170
- PROVIDER_CONFIG_NOT_FOUND: import("better-auth").RawError<"PROVIDER_CONFIG_NOT_FOUND">;
171
- PROVIDER_ID_REQUIRED: import("better-auth").RawError<"PROVIDER_ID_REQUIRED">;
172
- INVALID_OAUTH_CONFIG: import("better-auth").RawError<"INVALID_OAUTH_CONFIG">;
173
- SESSION_REQUIRED: import("better-auth").RawError<"SESSION_REQUIRED">;
174
- ISSUER_MISMATCH: import("better-auth").RawError<"ISSUER_MISMATCH">;
175
- ISSUER_MISSING: import("better-auth").RawError<"ISSUER_MISSING">;
176
- };
177
- }];
178
- };
1
+ /**
2
+ * Generic OAuth providers sign in through the standard `signIn.social` /
3
+ * `linkSocial` client methods (better-auth 1.7+), so no client plugin is
4
+ * required.
5
+ *
6
+ * @deprecated No longer needed — pass options directly to `createAuthClient`
7
+ * instead. Will be removed in a future major version.
8
+ */
9
+ export declare function getBetterAuthClientOptions(): {};
@@ -1,4 +1,4 @@
1
- export declare function getBetterAuthOptions({ oidcDiscoveryUrl, authSecret, authBaseURL, authClientId, authClientSecret, providerId }: {
1
+ export declare function getBetterAuthOptions({ oidcDiscoveryUrl, authSecret, authBaseURL, authClientId, authClientSecret, providerId, }: {
2
2
  oidcDiscoveryUrl: string;
3
3
  authSecret: string;
4
4
  authClientId: string;
@@ -28,163 +28,15 @@ export declare function getBetterAuthOptions({ oidcDiscoveryUrl, authSecret, aut
28
28
  readonly plugins: [{
29
29
  id: "generic-oauth";
30
30
  version: string;
31
- init: (ctx: import("better-auth").AuthContext) => {
31
+ init: (ctx: import("better-auth").AuthContext) => Promise<{
32
32
  context: {
33
- socialProviders: import("better-auth").OAuthProvider<Record<string, any>, Partial<import("better-auth").ProviderOptions<any>>>[];
33
+ socialProviders: import("better-auth").OAuthProvider<object, Partial<import("better-auth").ProviderOptions<object>>>[];
34
34
  };
35
- };
36
- endpoints: {
37
- signInWithOAuth2: import("better-call").StrictEndpoint<"/sign-in/oauth2", {
38
- method: "POST";
39
- body: import("zod").ZodObject<{
40
- providerId: import("zod").ZodString;
41
- callbackURL: import("zod").ZodOptional<import("zod").ZodString>;
42
- errorCallbackURL: import("zod").ZodOptional<import("zod").ZodString>;
43
- newUserCallbackURL: import("zod").ZodOptional<import("zod").ZodString>;
44
- disableRedirect: import("zod").ZodOptional<import("zod").ZodBoolean>;
45
- scopes: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString>>;
46
- requestSignUp: import("zod").ZodOptional<import("zod").ZodBoolean>;
47
- additionalData: import("zod").ZodOptional<import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodAny>>;
48
- }, import("zod/v4/core").$strip>;
49
- metadata: {
50
- openapi: {
51
- description: string;
52
- responses: {
53
- 200: {
54
- description: string;
55
- content: {
56
- "application/json": {
57
- schema: {
58
- type: "object";
59
- properties: {
60
- url: {
61
- type: string;
62
- };
63
- redirect: {
64
- type: string;
65
- };
66
- };
67
- };
68
- };
69
- };
70
- };
71
- };
72
- };
73
- };
74
- }, {
75
- url: string;
76
- redirect: boolean;
77
- }>;
78
- oAuth2Callback: import("better-call").StrictEndpoint<"/oauth2/callback/:providerId", {
79
- method: "GET";
80
- query: import("zod").ZodObject<{
81
- code: import("zod").ZodOptional<import("zod").ZodString>;
82
- error: import("zod").ZodOptional<import("zod").ZodString>;
83
- error_description: import("zod").ZodOptional<import("zod").ZodString>;
84
- state: import("zod").ZodOptional<import("zod").ZodString>;
85
- iss: import("zod").ZodOptional<import("zod").ZodString>;
86
- }, import("zod/v4/core").$strip>;
87
- metadata: {
88
- allowedMediaTypes: string[];
89
- openapi: {
90
- description: string;
91
- responses: {
92
- 200: {
93
- description: string;
94
- content: {
95
- "application/json": {
96
- schema: {
97
- type: "object";
98
- properties: {
99
- url: {
100
- type: string;
101
- };
102
- };
103
- };
104
- };
105
- };
106
- };
107
- };
108
- };
109
- scope: "server";
110
- };
111
- }, void>;
112
- oAuth2LinkAccount: import("better-call").StrictEndpoint<"/oauth2/link", {
113
- method: "POST";
114
- body: import("zod").ZodObject<{
115
- providerId: import("zod").ZodString;
116
- callbackURL: import("zod").ZodString;
117
- scopes: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString>>;
118
- errorCallbackURL: import("zod").ZodOptional<import("zod").ZodString>;
119
- }, import("zod/v4/core").$strip>;
120
- use: ((inputContext: import("better-call").MiddlewareInputContext<import("better-call").MiddlewareOptions>) => Promise<{
121
- session: {
122
- session: Record<string, any> & {
123
- id: string;
124
- createdAt: Date;
125
- updatedAt: Date;
126
- userId: string;
127
- expiresAt: Date;
128
- token: string;
129
- ipAddress?: string | null | undefined;
130
- userAgent?: string | null | undefined;
131
- };
132
- user: Record<string, any> & {
133
- id: string;
134
- createdAt: Date;
135
- updatedAt: Date;
136
- email: string;
137
- emailVerified: boolean;
138
- name: string;
139
- image?: string | null | undefined;
140
- };
141
- };
142
- }>)[];
143
- metadata: {
144
- openapi: {
145
- description: string;
146
- responses: {
147
- "200": {
148
- description: string;
149
- content: {
150
- "application/json": {
151
- schema: {
152
- type: "object";
153
- properties: {
154
- url: {
155
- type: string;
156
- format: string;
157
- description: string;
158
- };
159
- redirect: {
160
- type: string;
161
- description: string;
162
- enum: boolean[];
163
- };
164
- };
165
- required: string[];
166
- };
167
- };
168
- };
169
- };
170
- };
171
- };
172
- };
173
- }, {
174
- url: string;
175
- redirect: boolean;
176
- }>;
177
- };
178
- options: import("better-auth/plugins").GenericOAuthOptions;
35
+ }>;
36
+ options: import("better-auth/plugins").GenericOAuthOptions<string>;
179
37
  $ERROR_CODES: {
180
38
  INVALID_OAUTH_CONFIGURATION: import("better-auth").RawError<"INVALID_OAUTH_CONFIGURATION">;
181
39
  TOKEN_URL_NOT_FOUND: import("better-auth").RawError<"TOKEN_URL_NOT_FOUND">;
182
- PROVIDER_CONFIG_NOT_FOUND: import("better-auth").RawError<"PROVIDER_CONFIG_NOT_FOUND">;
183
- PROVIDER_ID_REQUIRED: import("better-auth").RawError<"PROVIDER_ID_REQUIRED">;
184
- INVALID_OAUTH_CONFIG: import("better-auth").RawError<"INVALID_OAUTH_CONFIG">;
185
- SESSION_REQUIRED: import("better-auth").RawError<"SESSION_REQUIRED">;
186
- ISSUER_MISMATCH: import("better-auth").RawError<"ISSUER_MISMATCH">;
187
- ISSUER_MISSING: import("better-auth").RawError<"ISSUER_MISSING">;
188
40
  };
189
41
  }, {
190
42
  id: "next-cookies";
@@ -192,11 +44,11 @@ export declare function getBetterAuthOptions({ oidcDiscoveryUrl, authSecret, aut
192
44
  hooks: {
193
45
  before: {
194
46
  matcher(ctx: import("better-auth").HookEndpointContext): boolean;
195
- handler: (inputContext: import("better-call").MiddlewareInputContext<import("better-call").MiddlewareOptions>) => Promise<void>;
47
+ handler: import("better-auth").Middleware<import("better-auth").MiddlewareOptions, (inputContext: import("better-auth").MiddlewareInputContext<import("better-auth").MiddlewareOptions>) => Promise<void>>;
196
48
  }[];
197
49
  after: {
198
50
  matcher(ctx: import("better-auth").HookEndpointContext): true;
199
- handler: (inputContext: import("better-call").MiddlewareInputContext<import("better-call").MiddlewareOptions>) => Promise<void>;
51
+ handler: import("better-auth").Middleware<import("better-auth").MiddlewareOptions, (inputContext: import("better-auth").MiddlewareInputContext<import("better-auth").MiddlewareOptions>) => Promise<void>>;
200
52
  }[];
201
53
  };
202
54
  }];
@@ -1,10 +1,15 @@
1
1
  import type { Auth } from "better-auth";
2
2
  import { headers as nextHeaders } from "next/headers";
3
3
  import type { NextRequest } from "next/server";
4
- export declare function getSignOutUrlRoute({ auth, discoveryUrl, federatedLogout, providerId, headers }: {
4
+ export declare function getSignOutUrlRoute({ auth, discoveryUrl, federatedLogout, headers, clientId, }: {
5
5
  auth: Auth;
6
6
  discoveryUrl?: string;
7
7
  federatedLogout?: boolean;
8
- providerId?: string;
9
8
  headers: typeof nextHeaders;
9
+ /**
10
+ * OIDC client ID, used to build the end-session URL when no ID token can be
11
+ * retrieved (for example, when the user never completed a sign-in, so no
12
+ * account cookie was ever stored). Required to recover from that case.
13
+ */
14
+ clientId?: string;
10
15
  }): (request: NextRequest) => Promise<Response>;
package/package.json CHANGED
@@ -1,29 +1,14 @@
1
1
  {
2
2
  "name": "@imtf/auth",
3
- "version": "1.1.0-beta.2",
3
+ "version": "2.0.0",
4
4
  "description": "Better auth wrapper for imtf usage",
5
5
  "license": "UNLICENSED",
6
6
  "author": "",
7
7
  "type": "module",
8
- "scripts": {
9
- "prebuild": "npm run build:clean",
10
- "build:clean": "rimraf lib",
11
- "build:js": "vite build",
12
- "build:types": "tsgo --project ./tsconfig.build.json && tsc-alias -p ./tsconfig.build.json",
13
- "build": "concurrently npm:build:js npm:build:types",
14
- "lint": "eslint src --cache --report-unused-disable-directives --max-warnings 0",
15
- "test": "vitest",
16
- "test:ui": "vitest --ui --coverage",
17
- "test:coverage": "vitest run --coverage",
18
- "typecheck": "tsgo --noEmit",
19
- "checks": "concurrently --kill-others-on-fail npm:lint npm:typecheck",
20
- "prepublishOnly": "npm run build && npm run checks"
21
- },
22
8
  "files": [
23
9
  "lib",
24
10
  "schemas"
25
11
  ],
26
- "prettier": "@imtf/prettier-config",
27
12
  "module": "./lib/index.esm.js",
28
13
  "types": "./lib/types/index.d.ts",
29
14
  "exports": {
@@ -33,18 +18,32 @@
33
18
  }
34
19
  },
35
20
  "dependencies": {
36
- "@imtf/panache": "^5.9.0"
21
+ "@imtf/panache": "^6.0.0"
37
22
  },
38
23
  "peerDependencies": {
39
- "@mui/lab": "^7.0.1-beta.18",
40
- "@mui/material": "^7.1.0",
24
+ "@mui/material": "^7.1.0 || ^9.4.0",
41
25
  "react": "^18.0.0 || ^19.0.0",
42
26
  "react-dom": "^18.0.0 || ^19.0.0",
43
- "better-auth": "^1.5.3"
27
+ "better-auth": "^1.7.1",
28
+ "zod": "^4.3.6"
44
29
  },
45
30
  "devDependencies": {
46
- "@mui/lab": "^7.0.1-beta.18",
47
- "@mui/material": "^7.1.0",
48
- "better-auth": "^1.5.3"
31
+ "@mui/material": "^9.4.0",
32
+ "better-auth": "^1.7.1",
33
+ "zod": "^4.3.6"
34
+ },
35
+ "scripts": {
36
+ "prebuild": "pnpm run build:clean",
37
+ "build:clean": "rimraf lib",
38
+ "build:js": "vite build",
39
+ "build:types": "tsc --project ./tsconfig.build.json && tsc-alias -p ./tsconfig.build.json",
40
+ "build": "concurrently pnpm:build:js pnpm:build:types",
41
+ "lint": "oxlint --type-aware --type-check --deny-warnings src",
42
+ "format:check": "oxfmt --check './src/**/*.{ts,tsx}'",
43
+ "typecheck": "tsc --noEmit",
44
+ "test": "vitest",
45
+ "test:ui": "vitest --ui --coverage",
46
+ "test:coverage": "vitest run --coverage",
47
+ "checks": "concurrently --kill-others-on-fail pnpm:lint pnpm:typecheck pnpm:format:check"
49
48
  }
50
- }
49
+ }