@imtf/auth 0.0.1-alpha.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/lib/constants.js +4 -0
- package/lib/hooks/useSignOut.js +54 -0
- package/lib/index.js +12 -0
- package/lib/options/auth-client.js +10 -0
- package/lib/options/auth.js +61 -0
- package/lib/routes/getSignOutUrlRoute.js +59 -0
- package/lib/types/constants.d.ts +4 -0
- package/lib/types/hooks/useSignOut.d.ts +23 -0
- package/lib/types/index.d.ts +5 -0
- package/lib/types/options/auth-client.d.ts +2 -0
- package/lib/types/options/auth.d.ts +9 -0
- package/lib/types/routes/getSignOutUrlRoute.d.ts +10 -0
- package/package.json +50 -0
package/lib/constants.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useCallback } from "react";
|
|
3
|
+
function useSignOut({
|
|
4
|
+
postLogoutRedirectPath = "/auth/sign-in?logout=success",
|
|
5
|
+
retrieveLogoutUrlPath = "/api/auth/sign-out-url",
|
|
6
|
+
navigate,
|
|
7
|
+
authClient,
|
|
8
|
+
origin = window.location.origin
|
|
9
|
+
}) {
|
|
10
|
+
const standardSignOut = useCallback(() => {
|
|
11
|
+
void authClient.signOut({
|
|
12
|
+
fetchOptions: {
|
|
13
|
+
onSuccess: () => {
|
|
14
|
+
navigate(postLogoutRedirectPath);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
}, [authClient, navigate, postLogoutRedirectPath]);
|
|
19
|
+
const handleSignOut = useCallback(async () => {
|
|
20
|
+
const postLogoutRedirectUri = `${origin}${postLogoutRedirectPath}`;
|
|
21
|
+
try {
|
|
22
|
+
const response = await fetch(
|
|
23
|
+
`${retrieveLogoutUrlPath}?post_logout_redirect_uri=${encodeURIComponent(postLogoutRedirectUri)}`
|
|
24
|
+
);
|
|
25
|
+
if (!response.ok) {
|
|
26
|
+
const { error } = await response.json().catch(() => ({ error: null }));
|
|
27
|
+
throw new Error(error ?? "Failed to create logout URL");
|
|
28
|
+
}
|
|
29
|
+
const { url } = await response.json();
|
|
30
|
+
if (!url) {
|
|
31
|
+
standardSignOut();
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
await authClient.signOut();
|
|
35
|
+
window.location.href = url;
|
|
36
|
+
} catch (error) {
|
|
37
|
+
console.error(
|
|
38
|
+
"Error during federated logout, switching to standard logout:",
|
|
39
|
+
error
|
|
40
|
+
);
|
|
41
|
+
standardSignOut();
|
|
42
|
+
}
|
|
43
|
+
}, [
|
|
44
|
+
authClient,
|
|
45
|
+
origin,
|
|
46
|
+
postLogoutRedirectPath,
|
|
47
|
+
retrieveLogoutUrlPath,
|
|
48
|
+
standardSignOut
|
|
49
|
+
]);
|
|
50
|
+
return { handleSignOut };
|
|
51
|
+
}
|
|
52
|
+
export {
|
|
53
|
+
useSignOut
|
|
54
|
+
};
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { getBetterAuthOptions } from "./options/auth.js";
|
|
2
|
+
import { getBetterAuthClientOptions } from "./options/auth-client.js";
|
|
3
|
+
import { getSignOutUrlRoute } from "./routes/getSignOutUrlRoute.js";
|
|
4
|
+
import { PROVIDER_ID } from "./constants.js";
|
|
5
|
+
import { useSignOut } from "./hooks/useSignOut.js";
|
|
6
|
+
export {
|
|
7
|
+
PROVIDER_ID,
|
|
8
|
+
getBetterAuthClientOptions,
|
|
9
|
+
getBetterAuthOptions,
|
|
10
|
+
getSignOutUrlRoute,
|
|
11
|
+
useSignOut
|
|
12
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { nextCookies } from "better-auth/next-js";
|
|
2
|
+
import { genericOAuth } from "better-auth/plugins";
|
|
3
|
+
import { PROVIDER_ID } from "../constants.js";
|
|
4
|
+
function getBetterAuthOptions({
|
|
5
|
+
oidcDiscoveryUrl,
|
|
6
|
+
authSecret,
|
|
7
|
+
authBaseURL,
|
|
8
|
+
authClientId,
|
|
9
|
+
authClientSecret,
|
|
10
|
+
providerId = PROVIDER_ID
|
|
11
|
+
}) {
|
|
12
|
+
return {
|
|
13
|
+
secret: authSecret,
|
|
14
|
+
baseURL: authBaseURL,
|
|
15
|
+
session: {
|
|
16
|
+
cookieCache: {
|
|
17
|
+
enabled: true,
|
|
18
|
+
version: "1",
|
|
19
|
+
maxAge: 60 * 60 * 24,
|
|
20
|
+
// Cache duration in seconds (1 day)
|
|
21
|
+
strategy: "compact",
|
|
22
|
+
refreshCache: true
|
|
23
|
+
// Enable stateless refresh
|
|
24
|
+
},
|
|
25
|
+
expiresIn: 60 * 60 * 24,
|
|
26
|
+
// Session expiration (1 day)
|
|
27
|
+
updateAge: 60 * 60,
|
|
28
|
+
// How often to update the session (1 hour)
|
|
29
|
+
freshAge: 60 * 5
|
|
30
|
+
// Consider session fresh for 5 minutes
|
|
31
|
+
},
|
|
32
|
+
account: {
|
|
33
|
+
updateAccountOnSignIn: true,
|
|
34
|
+
storeStateStrategy: "cookie",
|
|
35
|
+
storeAccountCookie: true
|
|
36
|
+
// Store account data in cookie (required for database-less flows)
|
|
37
|
+
},
|
|
38
|
+
plugins: [
|
|
39
|
+
genericOAuth({
|
|
40
|
+
config: [
|
|
41
|
+
{
|
|
42
|
+
providerId,
|
|
43
|
+
clientId: authClientId,
|
|
44
|
+
clientSecret: authClientSecret,
|
|
45
|
+
discoveryUrl: oidcDiscoveryUrl,
|
|
46
|
+
scopes: ["openid"],
|
|
47
|
+
pkce: true
|
|
48
|
+
}
|
|
49
|
+
]
|
|
50
|
+
}),
|
|
51
|
+
// Next.js cookie integration for server actions and middleware
|
|
52
|
+
nextCookies()
|
|
53
|
+
],
|
|
54
|
+
onAPIError: {
|
|
55
|
+
errorURL: "/auth/error"
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
export {
|
|
60
|
+
getBetterAuthOptions
|
|
61
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
function getSignOutUrlRoute({
|
|
2
|
+
auth,
|
|
3
|
+
federatedLogout = true,
|
|
4
|
+
discoveryUrl,
|
|
5
|
+
providerId,
|
|
6
|
+
headers
|
|
7
|
+
}) {
|
|
8
|
+
return async function GET(request) {
|
|
9
|
+
try {
|
|
10
|
+
if (!federatedLogout) {
|
|
11
|
+
return Response.json({ url: null }, { status: 200 });
|
|
12
|
+
}
|
|
13
|
+
const token = await auth.api.getAccessToken({
|
|
14
|
+
body: {
|
|
15
|
+
providerId
|
|
16
|
+
},
|
|
17
|
+
headers: await headers()
|
|
18
|
+
}).catch(() => null);
|
|
19
|
+
if (!token?.idToken) {
|
|
20
|
+
return Response.json(
|
|
21
|
+
{ error: "No ID token found for user" },
|
|
22
|
+
{ status: 400 }
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
const wellKnownResponse = await fetch(discoveryUrl);
|
|
26
|
+
if (!wellKnownResponse.ok) {
|
|
27
|
+
return Response.json(
|
|
28
|
+
{ error: "Failed to fetch OIDC discovery document" },
|
|
29
|
+
{ status: 500 }
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
const wellKnownConfig = await wellKnownResponse.json();
|
|
33
|
+
const endSessionEndpoint = wellKnownConfig.end_session_endpoint;
|
|
34
|
+
if (!endSessionEndpoint) {
|
|
35
|
+
return Response.json(
|
|
36
|
+
{ error: "Could not determine end_session_endpoint" },
|
|
37
|
+
{ status: 500 }
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
const logoutUrl = new URL(endSessionEndpoint);
|
|
41
|
+
const postLogoutRedirectUri = request.nextUrl.searchParams.get("post_logout_redirect_uri") ?? "/auth/sign-in";
|
|
42
|
+
logoutUrl.searchParams.set(
|
|
43
|
+
"post_logout_redirect_uri",
|
|
44
|
+
postLogoutRedirectUri
|
|
45
|
+
);
|
|
46
|
+
logoutUrl.searchParams.set("id_token_hint", token.idToken);
|
|
47
|
+
return Response.json({ url: logoutUrl.toString() });
|
|
48
|
+
} catch (error) {
|
|
49
|
+
console.error("Failed to construct logout URL:", error);
|
|
50
|
+
return Response.json(
|
|
51
|
+
{ error: "Failed to construct logout URL" },
|
|
52
|
+
{ status: 500 }
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
export {
|
|
58
|
+
getSignOutUrlRoute
|
|
59
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { AuthClient } from "better-auth/client";
|
|
2
|
+
import type { getBetterAuthClientOptions } from "../index";
|
|
3
|
+
/**
|
|
4
|
+
* Custom hook for handling user sign-out with support for federated logout.
|
|
5
|
+
*
|
|
6
|
+
* @param params - Configuration options
|
|
7
|
+
* @param params.postLogoutRedirectPath - Path to redirect to after logout (defaults to "/auth/sign-in")
|
|
8
|
+
* @returns Object containing handleSignOut function to initiate the logout process
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```tsx
|
|
12
|
+
* const { handleSignOut } = useSignOut({ postLogoutRedirectPath: "/auth/sign-in" });
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
export declare function useSignOut({ postLogoutRedirectPath, retrieveLogoutUrlPath, navigate, authClient, origin, }: {
|
|
16
|
+
postLogoutRedirectPath?: string;
|
|
17
|
+
retrieveLogoutUrlPath?: string;
|
|
18
|
+
navigate: (path: string) => void;
|
|
19
|
+
authClient: AuthClient<ReturnType<typeof getBetterAuthClientOptions>>;
|
|
20
|
+
origin?: string;
|
|
21
|
+
}): {
|
|
22
|
+
handleSignOut: () => Promise<void>;
|
|
23
|
+
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { getBetterAuthOptions } from "./options/auth";
|
|
2
|
+
export { getBetterAuthClientOptions } from "./options/auth-client";
|
|
3
|
+
export { getSignOutUrlRoute } from "./routes/getSignOutUrlRoute";
|
|
4
|
+
export { PROVIDER_ID } from "./constants";
|
|
5
|
+
export { useSignOut } from "./hooks/useSignOut";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type BetterAuthOptions } from "better-auth";
|
|
2
|
+
export declare function getBetterAuthOptions({ oidcDiscoveryUrl, authSecret, authBaseURL, authClientId, authClientSecret, providerId, }: {
|
|
3
|
+
oidcDiscoveryUrl: string;
|
|
4
|
+
authSecret: string;
|
|
5
|
+
authClientId: string;
|
|
6
|
+
authClientSecret: string;
|
|
7
|
+
providerId?: string;
|
|
8
|
+
authBaseURL?: string;
|
|
9
|
+
}): BetterAuthOptions;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Auth } from "better-auth";
|
|
2
|
+
import { headers as nextHeaders } from "next/headers";
|
|
3
|
+
import type { NextRequest } from "next/server";
|
|
4
|
+
export declare function getSignOutUrlRoute({ auth, federatedLogout, discoveryUrl, providerId, headers, }: {
|
|
5
|
+
auth: Auth;
|
|
6
|
+
federatedLogout?: boolean;
|
|
7
|
+
discoveryUrl: string;
|
|
8
|
+
providerId: string;
|
|
9
|
+
headers: typeof nextHeaders;
|
|
10
|
+
}): (request: NextRequest) => Promise<Response>;
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@imtf/auth",
|
|
3
|
+
"version": "0.0.1-alpha.0",
|
|
4
|
+
"description": "Better auth wrapper for imtf usage",
|
|
5
|
+
"license": "UNLICENSED",
|
|
6
|
+
"author": "",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"prebuild": "npm run build:clean",
|
|
10
|
+
"build:clean": "rimraf lib",
|
|
11
|
+
"build:js": "vite build",
|
|
12
|
+
"build:types": "tsc --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": "tsc --noEmit",
|
|
19
|
+
"checks": "concurrently --kill-others-on-fail npm:lint npm:typecheck",
|
|
20
|
+
"prepublishOnly": "npm run build && npm run checks"
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"lib",
|
|
24
|
+
"schemas"
|
|
25
|
+
],
|
|
26
|
+
"prettier": "@imtf/prettier-config",
|
|
27
|
+
"module": "./lib/index.js",
|
|
28
|
+
"types": "./lib/types/index.d.ts",
|
|
29
|
+
"exports": {
|
|
30
|
+
".": {
|
|
31
|
+
"types": "./lib/types/index.d.ts",
|
|
32
|
+
"import": "./lib/index.js"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"@imtf/panache": "^5.5.0"
|
|
37
|
+
},
|
|
38
|
+
"peerDependencies": {
|
|
39
|
+
"@mui/lab": "^7.0.1-beta.18",
|
|
40
|
+
"@mui/material": "^7.1.0",
|
|
41
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
42
|
+
"react-dom": "^18.0.0 || ^19.0.0",
|
|
43
|
+
"better-auth": "^1.4.18"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@mui/lab": "^7.0.1-beta.18",
|
|
47
|
+
"@mui/material": "^7.1.0",
|
|
48
|
+
"better-auth": "^1.4.18"
|
|
49
|
+
}
|
|
50
|
+
}
|