@dreamshive/better-auth-tauri 0.1.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/src/routes.ts ADDED
@@ -0,0 +1,67 @@
1
+ import { APIError, createAuthEndpoint } from "better-auth/api";
2
+ import { HIDE_METADATA } from "better-auth";
3
+ import * as z from "zod";
4
+
5
+ /**
6
+ * Server-side proxy that plants the OAuth CSRF state cookie in the system
7
+ * browser's cookie jar *before* redirecting to the provider's authorization
8
+ * URL. Without this, Better Auth's callback state-vs-cookie check fails in
9
+ * the Tauri flow because the state cookie was set on the `/sign-in/social`
10
+ * response inside the Tauri webview — a different cookie jar from the one
11
+ * the system browser uses for the actual OAuth dance.
12
+ *
13
+ * Two modes, matching the Expo plugin:
14
+ *
15
+ * 1. `oauthState` query param present → plant it as a plain `oauth_state`
16
+ * cookie (10 min TTL). Used when the client already has a stored state
17
+ * it wants re-seeded before starting the flow.
18
+ *
19
+ * 2. `oauthState` absent → extract the `state` query param from the
20
+ * authorization URL and plant it as a signed `state` cookie (5 min
21
+ * TTL). This is the common path for a fresh OAuth sign-in.
22
+ *
23
+ * Endpoint is hidden from the Better Auth route docs — it's an internal
24
+ * detail of the Tauri plugin flow.
25
+ */
26
+ export const tauriAuthorizationProxy = createAuthEndpoint(
27
+ "/tauri-authorization-proxy",
28
+ {
29
+ method: "GET",
30
+ query: z.object({
31
+ authorizationURL: z.string(),
32
+ oauthState: z.string().optional(),
33
+ }),
34
+ metadata: HIDE_METADATA,
35
+ },
36
+ async (ctx) => {
37
+ const { oauthState, authorizationURL } = ctx.query;
38
+
39
+ if (oauthState) {
40
+ const oauthStateCookie = ctx.context.createAuthCookie("oauth_state", {
41
+ maxAge: 600,
42
+ });
43
+ ctx.setCookie(
44
+ oauthStateCookie.name,
45
+ oauthState,
46
+ oauthStateCookie.attributes,
47
+ );
48
+ return ctx.redirect(authorizationURL);
49
+ }
50
+
51
+ const state = new URL(authorizationURL).searchParams.get("state");
52
+ if (!state) {
53
+ throw new APIError("BAD_REQUEST", {
54
+ message: "authorizationURL is missing required `state` query parameter",
55
+ });
56
+ }
57
+
58
+ const stateCookie = ctx.context.createAuthCookie("state", { maxAge: 300 });
59
+ await ctx.setSignedCookie(
60
+ stateCookie.name,
61
+ state,
62
+ ctx.context.secret,
63
+ stateCookie.attributes,
64
+ );
65
+ return ctx.redirect(authorizationURL);
66
+ },
67
+ );
package/src/utils.ts ADDED
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Detects whether the current runtime is a Tauri webview.
3
+ * During `vite dev` / `nuxt dev` in a normal browser, the Tauri IPC
4
+ * globals are absent — the client plugin should no-op its desktop
5
+ * branches in that case so the same code works in both environments.
6
+ */
7
+ export function isTauriRuntime(): boolean {
8
+ if (typeof window === "undefined") return false;
9
+ return (
10
+ "__TAURI_INTERNALS__" in window ||
11
+ "__TAURI__" in window ||
12
+ "__TAURI_METADATA__" in window
13
+ );
14
+ }
15
+
16
+ /**
17
+ * Build a deep-link URL from a scheme + path.
18
+ * `createSchemeURL("/auth/callback", "sokudo")` → `"sokudo://auth/callback"`
19
+ */
20
+ export function createSchemeURL(path: string, scheme: string): string {
21
+ const normalized = path.startsWith("/") ? path.slice(1) : path;
22
+ return `${scheme}://${normalized}`;
23
+ }
24
+
25
+ /**
26
+ * Origin equivalent for a custom scheme, e.g. `sokudo://`.
27
+ * Used for the `tauri-origin` header so the server can validate the request
28
+ * came from a trusted Tauri app build.
29
+ */
30
+ export function getOrigin(scheme: string): string {
31
+ return `${scheme}://`;
32
+ }
33
+
34
+ /** Forgiving JSON.parse that never throws. */
35
+ export function safeJSONParse<T = unknown>(input: string | null | undefined): T | null {
36
+ if (!input) return null;
37
+ try {
38
+ return JSON.parse(input) as T;
39
+ } catch {
40
+ return null;
41
+ }
42
+ }
package/src/version.ts ADDED
@@ -0,0 +1 @@
1
+ export const PACKAGE_VERSION = "0.1.0";