@better-auth/electron 1.5.0-beta.13 → 1.5.0-beta.15

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/dist/index.d.mts CHANGED
@@ -1,43 +1,10 @@
1
+ import { n as ElectronSharedOptions, t as ElectronOptions } from "./options-DTTv7_dq.mjs";
1
2
  import * as better_auth0 from "better-auth";
2
3
  import * as z from "zod";
4
+ import * as _better_auth_core0 from "@better-auth/core";
3
5
  import { HookEndpointContext } from "@better-auth/core";
4
6
  import * as better_call0 from "better-call";
5
7
 
6
- //#region src/types/options.d.ts
7
- interface ElectronOptions {
8
- /**
9
- * The duration (in seconds) for which the authorization code remains valid.
10
- *
11
- * @default 300 (5 minutes)
12
- */
13
- codeExpiresIn?: number | undefined;
14
- /**
15
- * The duration (in seconds) for which the redirect cookie remains valid.
16
- *
17
- * @default 120 (2 minutes)
18
- */
19
- redirectCookieExpiresIn?: number | undefined;
20
- /**
21
- * The prefix to use for cookies set by the plugin.
22
- *
23
- * @default "better-auth"
24
- */
25
- cookiePrefix?: string | undefined;
26
- /**
27
- * Client ID to use for identifying the Electron client during authorization.
28
- *
29
- * @default "electron"
30
- */
31
- clientID?: string | undefined;
32
- /**
33
- * Override the origin for Electron API routes.
34
- * Enable this if you're facing cors origin issues with Electron API routes.
35
- *
36
- * @default false
37
- */
38
- disableOriginOverride?: boolean | undefined;
39
- }
40
- //#endregion
41
8
  //#region src/index.d.ts
42
9
  declare module "@better-auth/core" {
43
10
  interface BetterAuthPluginRegistry<AuthOptions, Options> {
@@ -170,7 +137,7 @@ declare const electron: (options?: ElectronOptions | undefined) => {
170
137
  minPasswordLength: number;
171
138
  maxPasswordLength: number;
172
139
  };
173
- checkPassword: (userId: string, ctx: better_auth0.GenericEndpointContext<better_auth0.BetterAuthOptions>) => Promise<boolean>;
140
+ checkPassword: (userId: string, ctx: _better_auth_core0.GenericEndpointContext<better_auth0.BetterAuthOptions>) => Promise<boolean>;
174
141
  };
175
142
  tables: better_auth0.BetterAuthDBSchema;
176
143
  runMigrations: () => Promise<void>;
@@ -196,6 +163,32 @@ declare const electron: (options?: ElectronOptions | undefined) => {
196
163
  }, z.core.$strip>;
197
164
  metadata: {
198
165
  scope: "http";
166
+ openapi: {
167
+ description: string;
168
+ operationId: string;
169
+ responses: {
170
+ 200: {
171
+ description: string;
172
+ content: {
173
+ "application/json": {
174
+ schema: {
175
+ type: "object";
176
+ properties: {
177
+ token: {
178
+ type: string;
179
+ };
180
+ user: {
181
+ type: string;
182
+ $ref: string;
183
+ };
184
+ };
185
+ required: string[];
186
+ };
187
+ };
188
+ };
189
+ };
190
+ };
191
+ };
199
192
  };
200
193
  }, {
201
194
  token: string;
@@ -211,6 +204,39 @@ declare const electron: (options?: ElectronOptions | undefined) => {
211
204
  }, z.core.$strip>;
212
205
  metadata: {
213
206
  scope: "http";
207
+ openapi: {
208
+ description: string;
209
+ operationId: string;
210
+ responses: {
211
+ 200: {
212
+ description: string;
213
+ content: {
214
+ "application/json": {
215
+ schema: {
216
+ type: "object";
217
+ properties: {
218
+ url: {
219
+ type: string;
220
+ nullable: boolean;
221
+ };
222
+ redirect: {
223
+ type: string;
224
+ };
225
+ user: {
226
+ type: string;
227
+ $ref: string;
228
+ };
229
+ token: {
230
+ type: string;
231
+ };
232
+ };
233
+ required: string[];
234
+ };
235
+ };
236
+ };
237
+ };
238
+ };
239
+ };
214
240
  };
215
241
  }, {
216
242
  url: string | undefined;
@@ -218,16 +244,84 @@ declare const electron: (options?: ElectronOptions | undefined) => {
218
244
  user?: better_auth0.User & Record<string, any>;
219
245
  token?: string;
220
246
  } | undefined>;
247
+ electronTransferUser: better_call0.StrictEndpoint<"/electron/transfer-user", {
248
+ method: "POST";
249
+ query: z.ZodObject<{
250
+ client_id: z.ZodString;
251
+ state: z.ZodString;
252
+ code_challenge: z.ZodString;
253
+ code_challenge_method: z.ZodOptional<z.ZodString>;
254
+ }, z.core.$strip>;
255
+ body: z.ZodObject<{
256
+ callbackURL: z.ZodOptional<z.ZodString>;
257
+ }, z.core.$strip>;
258
+ use: ((inputContext: better_call0.MiddlewareInputContext<better_call0.MiddlewareOptions>) => Promise<{
259
+ session: {
260
+ session: Record<string, any> & {
261
+ id: string;
262
+ createdAt: Date;
263
+ updatedAt: Date;
264
+ userId: string;
265
+ expiresAt: Date;
266
+ token: string;
267
+ ipAddress?: string | null | undefined;
268
+ userAgent?: string | null | undefined;
269
+ };
270
+ user: Record<string, any> & {
271
+ id: string;
272
+ createdAt: Date;
273
+ updatedAt: Date;
274
+ email: string;
275
+ emailVerified: boolean;
276
+ name: string;
277
+ image?: string | null | undefined;
278
+ };
279
+ };
280
+ }>)[];
281
+ requireHeaders: true;
282
+ metadata: {
283
+ openapi: {
284
+ description: string;
285
+ operationId: string;
286
+ responses: {
287
+ 200: {
288
+ description: string;
289
+ content: {
290
+ "application/json": {
291
+ schema: {
292
+ type: "object";
293
+ properties: {
294
+ url: {
295
+ type: string;
296
+ nullable: boolean;
297
+ };
298
+ redirect: {
299
+ type: string;
300
+ };
301
+ };
302
+ required: string[];
303
+ };
304
+ };
305
+ };
306
+ };
307
+ };
308
+ };
309
+ };
310
+ }, {
311
+ url: string | null;
312
+ redirect: boolean;
313
+ }>;
221
314
  };
222
315
  options: {
223
316
  codeExpiresIn: number;
224
317
  redirectCookieExpiresIn: number;
225
318
  cookiePrefix: string;
226
- clientID: string;
227
319
  disableOriginOverride?: boolean | undefined;
320
+ clientID: string;
228
321
  };
229
322
  $ERROR_CODES: {
230
323
  INVALID_TOKEN: better_auth0.RawError<"INVALID_TOKEN">;
324
+ INVALID_CLIENT_ID: better_auth0.RawError<"INVALID_CLIENT_ID">;
231
325
  STATE_MISMATCH: better_auth0.RawError<"STATE_MISMATCH">;
232
326
  MISSING_CODE_CHALLENGE: better_auth0.RawError<"MISSING_CODE_CHALLENGE">;
233
327
  INVALID_CODE_VERIFIER: better_auth0.RawError<"INVALID_CODE_VERIFIER">;
@@ -236,4 +330,4 @@ declare const electron: (options?: ElectronOptions | undefined) => {
236
330
  };
237
331
  };
238
332
  //#endregion
239
- export { ElectronOptions, electron };
333
+ export { ElectronOptions, ElectronSharedOptions, electron };
package/dist/index.mjs CHANGED
@@ -10,12 +10,13 @@ import { safeJSONParse as safeJSONParse$1 } from "@better-auth/core/utils/json";
10
10
  import { base64Url } from "@better-auth/utils/base64";
11
11
  import { createHash } from "@better-auth/utils/hash";
12
12
  import { betterFetch } from "@better-fetch/fetch";
13
- import { createAuthEndpoint } from "better-auth/api";
13
+ import { createAuthEndpoint, sessionMiddleware } from "better-auth/api";
14
14
  import { setSessionCookie } from "better-auth/cookies";
15
15
  import { parseUserOutput } from "better-auth/db";
16
16
 
17
17
  //#region src/error-codes.ts
18
18
  const ELECTRON_ERROR_CODES = defineErrorCodes({
19
+ INVALID_CLIENT_ID: "Invalid client ID",
19
20
  INVALID_TOKEN: "Invalid or expired token.",
20
21
  STATE_MISMATCH: "state mismatch",
21
22
  MISSING_CODE_CHALLENGE: "missing code challenge",
@@ -31,10 +32,30 @@ const electronTokenBodySchema = z.object({
31
32
  state: z.string().nonempty(),
32
33
  code_verifier: z.string().nonempty()
33
34
  });
34
- const electronToken = (opts) => createAuthEndpoint("/electron/token", {
35
+ const electronToken = (_opts) => createAuthEndpoint("/electron/token", {
35
36
  method: "POST",
36
37
  body: electronTokenBodySchema,
37
- metadata: { scope: "http" }
38
+ metadata: {
39
+ scope: "http",
40
+ openapi: {
41
+ description: "Exchange the electron token for a session",
42
+ operationId: "electronToken",
43
+ responses: { 200: {
44
+ description: "Returns the session token and user",
45
+ content: { "application/json": { schema: {
46
+ type: "object",
47
+ properties: {
48
+ token: { type: "string" },
49
+ user: {
50
+ type: "object",
51
+ $ref: "#/components/schemas/User"
52
+ }
53
+ },
54
+ required: ["token", "user"]
55
+ } } }
56
+ } }
57
+ }
58
+ }
38
59
  }, async (ctx) => {
39
60
  const token = await ctx.context.internalAdapter.findVerificationValue(`electron:${ctx.body.token}`);
40
61
  if (!token || token.expiresAt < /* @__PURE__ */ new Date()) throw APIError.from("NOT_FOUND", ELECTRON_ERROR_CODES.INVALID_TOKEN);
@@ -70,7 +91,32 @@ const electronInitOAuthProxyQuerySchema = z.object({
70
91
  const electronInitOAuthProxy = (opts) => createAuthEndpoint("/electron/init-oauth-proxy", {
71
92
  method: "GET",
72
93
  query: electronInitOAuthProxyQuerySchema,
73
- metadata: { scope: "http" }
94
+ metadata: {
95
+ scope: "http",
96
+ openapi: {
97
+ description: "Initialize the OAuth proxy for the electron app",
98
+ operationId: "electronInitOAuthProxy",
99
+ responses: { 200: {
100
+ description: "Returns the URL to redirect to and if the redirect should be performed",
101
+ content: { "application/json": { schema: {
102
+ type: "object",
103
+ properties: {
104
+ url: {
105
+ type: "string",
106
+ nullable: true
107
+ },
108
+ redirect: { type: "boolean" },
109
+ user: {
110
+ type: "object",
111
+ $ref: "#/components/schemas/User"
112
+ },
113
+ token: { type: "string" }
114
+ },
115
+ required: ["url", "redirect"]
116
+ } } }
117
+ } }
118
+ }
119
+ }
74
120
  }, async (ctx) => {
75
121
  const isSocialProvider = SocialProviderListEnum.safeParse(ctx.query.provider);
76
122
  if (!isSocialProvider && !ctx.context.getPlugin("generic-oauth")) throw APIError.from("BAD_REQUEST", BASE_ERROR_CODES.PROVIDER_NOT_FOUND);
@@ -103,6 +149,44 @@ const electronInitOAuthProxy = (opts) => createAuthEndpoint("/electron/init-oaut
103
149
  }
104
150
  return ctx.json(res.data);
105
151
  });
152
+ const electronTransferUserQuerySchema = z.object({
153
+ client_id: z.string(),
154
+ state: z.string(),
155
+ code_challenge: z.string(),
156
+ code_challenge_method: z.string().optional()
157
+ });
158
+ const electronTransferUserBodySchema = z.object({ callbackURL: z.string().optional() });
159
+ const electronTransferUser = (_opts, { handleTransfer }) => createAuthEndpoint("/electron/transfer-user", {
160
+ method: "POST",
161
+ query: electronTransferUserQuerySchema,
162
+ body: electronTransferUserBodySchema,
163
+ use: [sessionMiddleware],
164
+ requireHeaders: true,
165
+ metadata: { openapi: {
166
+ description: "Transfer the user to the electron app",
167
+ operationId: "electronTransferUser",
168
+ responses: { 200: {
169
+ description: "Returns the URL to redirect to and if the redirect should be performed",
170
+ content: { "application/json": { schema: {
171
+ type: "object",
172
+ properties: {
173
+ url: {
174
+ type: "string",
175
+ nullable: true
176
+ },
177
+ redirect: { type: "boolean" }
178
+ },
179
+ required: ["url", "redirect"]
180
+ } } }
181
+ } }
182
+ } }
183
+ }, async (ctx) => {
184
+ if (!await handleTransfer(ctx, ctx.query)) throw APIError.from("BAD_REQUEST", ELECTRON_ERROR_CODES.INVALID_CLIENT_ID);
185
+ return ctx.json({
186
+ url: ctx.body.callbackURL ? ctx.body.callbackURL : null,
187
+ redirect: ctx.body.callbackURL ? true : false
188
+ });
189
+ });
106
190
 
107
191
  //#endregion
108
192
  //#region src/index.ts
@@ -117,6 +201,33 @@ const electron = (options) => {
117
201
  const hookMatcher = (ctx) => {
118
202
  return !!(ctx.path?.startsWith("/sign-in") || ctx.path?.startsWith("/sign-up") || ctx.path?.startsWith("/callback") || ctx.path?.startsWith("/oauth2/callback") || ctx.path?.startsWith("/magic-link/verify") || ctx.path?.startsWith("/email-otp/verify-email") || ctx.path?.startsWith("/verify-email") || ctx.path?.startsWith("/one-tap/callback") || ctx.path?.startsWith("/passkey/verify-authentication") || ctx.path?.startsWith("/phone-number/verify"));
119
203
  };
204
+ const handleTransfer = async (ctx, payload) => {
205
+ const { client_id, state, code_challenge, code_challenge_method = "plain" } = payload;
206
+ const userId = ctx.context.session?.user.id || ctx.context.newSession?.user.id;
207
+ if (!userId || client_id !== opts.clientID) return false;
208
+ if (!state) throw APIError.from("BAD_REQUEST", ELECTRON_ERROR_CODES.MISSING_STATE);
209
+ if (!code_challenge) throw APIError.from("BAD_REQUEST", ELECTRON_ERROR_CODES.MISSING_PKCE);
210
+ const redirectCookieName = `${opts.cookiePrefix}.${opts.clientID}`;
211
+ const identifier = generateRandomString(32, "a-z", "A-Z", "0-9");
212
+ const codeExpiresInMs = opts.codeExpiresIn * 1e3;
213
+ const expiresAt = new Date(Date.now() + codeExpiresInMs);
214
+ await ctx.context.internalAdapter.createVerificationValue({
215
+ identifier: `electron:${identifier}`,
216
+ value: JSON.stringify({
217
+ userId,
218
+ codeChallenge: code_challenge,
219
+ codeChallengeMethod: code_challenge_method.toLowerCase(),
220
+ state
221
+ }),
222
+ expiresAt
223
+ });
224
+ ctx.setCookie(redirectCookieName, identifier, {
225
+ ...ctx.context.authCookies.sessionToken.attributes,
226
+ maxAge: opts.redirectCookieExpiresIn,
227
+ httpOnly: false
228
+ });
229
+ return true;
230
+ };
120
231
  return {
121
232
  id: "electron",
122
233
  async onRequest(request, _ctx) {
@@ -162,35 +273,14 @@ const electron = (options) => {
162
273
  if (query.success) transferPayload = query.data;
163
274
  }
164
275
  if (!transferPayload) return;
165
- const { client_id, code_challenge, code_challenge_method, state } = transferPayload;
166
- if (client_id !== opts.clientID) return;
167
- if (!state) throw APIError.from("BAD_REQUEST", ELECTRON_ERROR_CODES.MISSING_STATE);
168
- if (!code_challenge) throw APIError.from("BAD_REQUEST", ELECTRON_ERROR_CODES.MISSING_PKCE);
169
- const redirectCookieName = `${opts.cookiePrefix}.${opts.clientID}`;
170
- const identifier = generateRandomString(32, "a-z", "A-Z", "0-9");
171
- const codeExpiresInMs = opts.codeExpiresIn * 1e3;
172
- const expiresAt = new Date(Date.now() + codeExpiresInMs);
173
- await ctx.context.internalAdapter.createVerificationValue({
174
- identifier: `electron:${identifier}`,
175
- value: JSON.stringify({
176
- userId: ctx.context.newSession.user.id,
177
- codeChallenge: code_challenge,
178
- codeChallengeMethod: code_challenge_method.toLowerCase(),
179
- state
180
- }),
181
- expiresAt
182
- });
183
- ctx.setCookie(redirectCookieName, identifier, {
184
- ...ctx.context.authCookies.sessionToken.attributes,
185
- maxAge: opts.redirectCookieExpiresIn,
186
- httpOnly: false
187
- });
276
+ await handleTransfer(ctx, transferPayload);
188
277
  return ctx;
189
278
  })
190
279
  }] },
191
280
  endpoints: {
192
281
  electronToken: electronToken(opts),
193
- electronInitOAuthProxy: electronInitOAuthProxy(opts)
282
+ electronInitOAuthProxy: electronInitOAuthProxy(opts),
283
+ electronTransferUser: electronTransferUser(opts, { handleTransfer })
194
284
  },
195
285
  options: opts,
196
286
  $ERROR_CODES: ELECTRON_ERROR_CODES
@@ -0,0 +1,38 @@
1
+ //#region src/types/options.d.ts
2
+ interface ElectronSharedOptions {
3
+ /**
4
+ * Client ID to use for identifying the Electron client during authorization.
5
+ *
6
+ * @default "electron"
7
+ */
8
+ clientID?: string | undefined;
9
+ }
10
+ interface ElectronOptions extends ElectronSharedOptions {
11
+ /**
12
+ * The duration (in seconds) for which the authorization code remains valid.
13
+ *
14
+ * @default 300 (5 minutes)
15
+ */
16
+ codeExpiresIn?: number | undefined;
17
+ /**
18
+ * The duration (in seconds) for which the redirect cookie remains valid.
19
+ *
20
+ * @default 120 (2 minutes)
21
+ */
22
+ redirectCookieExpiresIn?: number | undefined;
23
+ /**
24
+ * The prefix to use for cookies set by the plugin.
25
+ *
26
+ * @default "better-auth"
27
+ */
28
+ cookiePrefix?: string | undefined;
29
+ /**
30
+ * Override the origin for Electron API routes.
31
+ * Enable this if you're facing cors origin issues with Electron API routes.
32
+ *
33
+ * @default false
34
+ */
35
+ disableOriginOverride?: boolean | undefined;
36
+ }
37
+ //#endregion
38
+ export { ElectronSharedOptions as n, ElectronOptions as t };