better-auth 0.2.3-beta.8 → 0.2.4

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.
@@ -1,2 +1,527 @@
1
- import{atom as g}from"nanostores";var u=class extends Error{path;constructor(e,a){super(e),this.path=a}},m=class{constructor(e){this.s=e;this.statements=e}statements;newRole(e){return new f(e)}},f=class r{statements;constructor(e){this.statements=e}authorize(e,a){for(let[i,t]of Object.entries(e)){let s=this.statements[i];if(!s)return{success:!1,error:`You are not allowed to access resource: ${i}`};let o=a==="OR"?t.some(n=>s.includes(n)):t.every(n=>s.includes(n));return o?{success:o}:{success:!1,error:`unauthorized to access resource "${i}"`}}return{success:!1,error:"Not authorized"}}static fromString(e){let a=JSON.parse(e);if(typeof a!="object")throw new u("statements is not an object",".");for(let[i,t]of Object.entries(a)){if(typeof i!="string")throw new u("invalid resource identifier",i);if(!Array.isArray(t))throw new u("actions is not an array",i);for(let s=0;s<t.length;s++)if(typeof t[s]!="string")throw new u("action is not a string",`${i}[${s}]`)}return new r(a)}toString(){return JSON.stringify(this.statements)}};var h=r=>new m(r),P={organization:["update","delete"],member:["create","update","delete"],invitation:["create","cancel"]},d=h(P),U=d.newRole({organization:["update"],invitation:["create","cancel"],member:["create","update","delete"]}),v=d.newRole({organization:["update","delete"],member:["create","update","delete"],invitation:["create","cancel"]}),E=d.newRole({organization:[],member:[],invitation:[]});import{createFetch as ee}from"@better-fetch/fetch";import"nanostores";import{betterFetch as K}from"@better-fetch/fetch";import{atom as me}from"nanostores";import"@better-fetch/fetch";import{atom as A,onMount as O}from"nanostores";var p=(r,e,a,i)=>{let t=A({data:null,error:null,isPending:!1}),s=()=>{let n=typeof i=="function"?i({data:t.get().data,error:t.get().error,isPending:t.get().isPending}):i;return a(e,{...n,onSuccess:async c=>{t.set({data:c.data,error:null,isPending:!1}),await n?.onSuccess?.(c)},async onError(c){t.set({error:c.error,data:null,isPending:!1}),await n?.onError?.(c)},async onRequest(c){let y=t.get();t.set({isPending:!0,data:y.data,error:y.error}),await n?.onRequest?.(c)}})};r=Array.isArray(r)?r:[r];let o=!1;for(let n of r)n.subscribe(()=>{o?s():O(t,()=>(s(),o=!0,()=>{t.off(),n.off()}))});return t};var ve=r=>{let e=g(void 0),a=g(!1),i=g(!1);return{id:"organization",$InferServerPlugin:{},getActions:t=>({$Infer:{ActiveOrganization:{},Organization:{},Invitation:{},Member:{}},organization:{setActive(s){e.set(s)},hasPermission:async s=>await t("/organization/has-permission",{method:"POST",body:{permission:s.permission},...s.fetchOptions})}}),getAtoms:t=>{let s=p(a,"/organization/list",t,{method:"GET"}),o=p([e,i],"/organization/activate",t,()=>({method:"POST",credentials:"include",body:{orgId:e.get()}}));return{_listOrg:a,_activeOrgSignal:i,activeOrganization:o,listOrganizations:s}},atomListeners:[{matcher(t){return t==="/organization/create"||t==="/organization/delete"},signal:"_listOrg"},{matcher(t){return t.startsWith("/organization")},signal:"_activeOrgSignal"}]}};var Ie=()=>({id:"username",$InferServerPlugin:{}});import{WebAuthnError as T,startAuthentication as w,startRegistration as x}from"@simplewebauthn/browser";import{createConsola as b}from"consola";var l=b({formatOptions:{date:!1,colors:!0,compact:!0},defaults:{tag:"Better Auth"}}),R=r=>({log:(...e)=>{!r?.disabled&&l.log("",...e)},error:(...e)=>{!r?.disabled&&l.error("",...e)},warn:(...e)=>{!r?.disabled&&l.warn("",...e)},info:(...e)=>{!r?.disabled&&l.info("",...e)},debug:(...e)=>{!r?.disabled&&l.debug("",...e)},box:(...e)=>{!r?.disabled&&l.box("",...e)},success:(...e)=>{!r?.disabled&&l.success("",...e)},break:(...e)=>{!r?.disabled&&console.log(`
2
- `)}}),ze=R();import{atom as k}from"nanostores";var C=(r,{_listPasskeys:e})=>({signIn:{passkey:async(t,s)=>{let o=await r("/passkey/generate-authenticate-options",{method:"POST",body:{email:t?.email,callbackURL:t?.callbackURL}});if(!o.data)return o;try{let n=await w(o.data,t?.autoFill||!1),c=await r("/passkey/verify-authentication",{body:{response:n},...t?.fetchOptions,...s});if(!c.data)return c}catch(n){console.log(n)}}},passkey:{addPasskey:async(t,s)=>{let o=await r("/passkey/generate-register-options",{method:"GET"});if(!o.data)return o;try{let n=await x(o.data),c=await r("/passkey/verify-registration",{...t?.fetchOptions,...s,body:{response:n,name:t?.name}});if(!c.data)return c;e.set(Math.random())}catch(n){return n instanceof T?n.code==="ERROR_AUTHENTICATOR_PREVIOUSLY_REGISTERED"?{data:null,error:{message:"previously registered",status:400,statusText:"BAD_REQUEST"}}:n.code==="ERROR_CEREMONY_ABORTED"?{data:null,error:{message:"registration cancelled",status:400,statusText:"BAD_REQUEST"}}:{data:null,error:{message:n.message,status:400,statusText:"BAD_REQUEST"}}:{data:null,error:{message:n instanceof Error?n.message:"unknown error",status:500,statusText:"INTERNAL_SERVER_ERROR"}}}}},$Infer:{}}),De=()=>{let r=k();return{id:"passkey",$InferServerPlugin:{},getActions:e=>C(e,{_listPasskeys:r}),getAtoms(e){return{listPasskeys:p(r,"/passkey/list-user-passkeys",e,{method:"GET",credentials:"include"}),_listPasskeys:r}},pathMethods:{"/passkey/register":"POST","/passkey/authenticate":"POST"},atomListeners:[{matcher(e){return e==="/passkey/verify-registration"||e==="/passkey/delete-passkey"},signal:"_listPasskeys"}]}};var Ke=(r={redirect:!0,twoFactorPage:"/"})=>({id:"two-factor",$InferServerPlugin:{},atomListeners:[{matcher:e=>e==="/two-factor/enable"||e==="/two-factor/send-otp"||e==="/two-factor/disable",signal:"_sessionSignal"}],pathMethods:{"/two-factor/disable":"POST","/two-factor/enable":"POST","/two-factor/send-otp":"POST"},fetchPlugins:[{id:"two-factor",name:"two-factor",hooks:{async onSuccess(e){e.data?.twoFactorRedirect&&(r.redirect||r.twoFactorPage)&&typeof window<"u"&&(window.location.href=r.twoFactorPage)}}}]});var Je=()=>({id:"magic-link",$InferServerPlugin:{}});export{C as getPasskeyActions,Je as magicLinkClient,ve as organizationClient,De as passkeyClient,Ke as twoFactorClient,Ie as usernameClient};
1
+ // src/plugins/organization/client.ts
2
+ import { atom as atom3 } from "nanostores";
3
+
4
+ // src/plugins/organization/access/src/access.ts
5
+ var ParsingError = class extends Error {
6
+ path;
7
+ constructor(message, path) {
8
+ super(message);
9
+ this.path = path;
10
+ }
11
+ };
12
+ var AccessControl = class {
13
+ constructor(s) {
14
+ this.s = s;
15
+ this.statements = s;
16
+ }
17
+ statements;
18
+ newRole(statements) {
19
+ return new Role(statements);
20
+ }
21
+ };
22
+ var Role = class _Role {
23
+ statements;
24
+ constructor(statements) {
25
+ this.statements = statements;
26
+ }
27
+ authorize(request, connector) {
28
+ for (const [requestedResource, requestedActions] of Object.entries(
29
+ request
30
+ )) {
31
+ const allowedActions = this.statements[requestedResource];
32
+ if (!allowedActions) {
33
+ return {
34
+ success: false,
35
+ error: `You are not allowed to access resource: ${requestedResource}`
36
+ };
37
+ }
38
+ const success = connector === "OR" ? requestedActions.some(
39
+ (requestedAction) => allowedActions.includes(requestedAction)
40
+ ) : requestedActions.every(
41
+ (requestedAction) => allowedActions.includes(requestedAction)
42
+ );
43
+ if (success) {
44
+ return { success };
45
+ }
46
+ return {
47
+ success: false,
48
+ error: `unauthorized to access resource "${requestedResource}"`
49
+ };
50
+ }
51
+ return {
52
+ success: false,
53
+ error: "Not authorized"
54
+ };
55
+ }
56
+ static fromString(s) {
57
+ const statements = JSON.parse(s);
58
+ if (typeof statements !== "object") {
59
+ throw new ParsingError("statements is not an object", ".");
60
+ }
61
+ for (const [resource, actions] of Object.entries(statements)) {
62
+ if (typeof resource !== "string") {
63
+ throw new ParsingError("invalid resource identifier", resource);
64
+ }
65
+ if (!Array.isArray(actions)) {
66
+ throw new ParsingError("actions is not an array", resource);
67
+ }
68
+ for (let i = 0; i < actions.length; i++) {
69
+ if (typeof actions[i] !== "string") {
70
+ throw new ParsingError("action is not a string", `${resource}[${i}]`);
71
+ }
72
+ }
73
+ }
74
+ return new _Role(statements);
75
+ }
76
+ toString() {
77
+ return JSON.stringify(this.statements);
78
+ }
79
+ };
80
+
81
+ // src/plugins/organization/access/statement.ts
82
+ var createAccessControl = (statements) => {
83
+ return new AccessControl(statements);
84
+ };
85
+ var defaultStatements = {
86
+ organization: ["update", "delete"],
87
+ member: ["create", "update", "delete"],
88
+ invitation: ["create", "cancel"]
89
+ };
90
+ var defaultAc = createAccessControl(defaultStatements);
91
+ var adminAc = defaultAc.newRole({
92
+ organization: ["update"],
93
+ invitation: ["create", "cancel"],
94
+ member: ["create", "update", "delete"]
95
+ });
96
+ var ownerAc = defaultAc.newRole({
97
+ organization: ["update", "delete"],
98
+ member: ["create", "update", "delete"],
99
+ invitation: ["create", "cancel"]
100
+ });
101
+ var memberAc = defaultAc.newRole({
102
+ organization: [],
103
+ member: [],
104
+ invitation: []
105
+ });
106
+
107
+ // src/client/config.ts
108
+ import { createFetch } from "@better-fetch/fetch";
109
+ import "nanostores";
110
+
111
+ // src/client/fetch-plugins.ts
112
+ import { betterFetch } from "@better-fetch/fetch";
113
+
114
+ // src/client/session-atom.ts
115
+ import { atom as atom2 } from "nanostores";
116
+
117
+ // src/client/query.ts
118
+ import "@better-fetch/fetch";
119
+ import { atom, onMount } from "nanostores";
120
+ var useAuthQuery = (initializedAtom, path, $fetch, options) => {
121
+ const value = atom({
122
+ data: null,
123
+ error: null,
124
+ isPending: false
125
+ });
126
+ const fn = () => {
127
+ const opts = typeof options === "function" ? options({
128
+ data: value.get().data,
129
+ error: value.get().error,
130
+ isPending: value.get().isPending
131
+ }) : options;
132
+ return $fetch(path, {
133
+ ...opts,
134
+ onSuccess: async (context) => {
135
+ value.set({
136
+ data: context.data,
137
+ error: null,
138
+ isPending: false
139
+ });
140
+ await opts?.onSuccess?.(context);
141
+ },
142
+ async onError(context) {
143
+ value.set({
144
+ error: context.error,
145
+ data: null,
146
+ isPending: false
147
+ });
148
+ await opts?.onError?.(context);
149
+ },
150
+ async onRequest(context) {
151
+ const currentValue = value.get();
152
+ value.set({
153
+ isPending: true,
154
+ data: currentValue.data,
155
+ error: currentValue.error
156
+ });
157
+ await opts?.onRequest?.(context);
158
+ }
159
+ });
160
+ };
161
+ initializedAtom = Array.isArray(initializedAtom) ? initializedAtom : [initializedAtom];
162
+ let isMounted = false;
163
+ for (const initAtom of initializedAtom) {
164
+ initAtom.subscribe(() => {
165
+ if (isMounted) {
166
+ fn();
167
+ } else {
168
+ onMount(value, () => {
169
+ fn();
170
+ isMounted = true;
171
+ return () => {
172
+ value.off();
173
+ initAtom.off();
174
+ };
175
+ });
176
+ }
177
+ });
178
+ }
179
+ return value;
180
+ };
181
+
182
+ // src/plugins/organization/client.ts
183
+ var organizationClient = (options) => {
184
+ const activeOrgId = atom3(void 0);
185
+ const _listOrg = atom3(false);
186
+ const _activeOrgSignal = atom3(false);
187
+ return {
188
+ id: "organization",
189
+ $InferServerPlugin: {},
190
+ getActions: ($fetch) => ({
191
+ $Infer: {
192
+ ActiveOrganization: {},
193
+ Organization: {},
194
+ Invitation: {},
195
+ Member: {}
196
+ },
197
+ organization: {
198
+ setActive(orgId) {
199
+ activeOrgId.set(orgId);
200
+ },
201
+ hasPermission: async (data) => {
202
+ return await $fetch("/organization/has-permission", {
203
+ method: "POST",
204
+ body: {
205
+ permission: data.permission
206
+ },
207
+ ...data.fetchOptions
208
+ });
209
+ }
210
+ }
211
+ }),
212
+ getAtoms: ($fetch) => {
213
+ const listOrganizations = useAuthQuery(
214
+ _listOrg,
215
+ "/organization/list",
216
+ $fetch,
217
+ {
218
+ method: "GET"
219
+ }
220
+ );
221
+ const activeOrganization = useAuthQuery(
222
+ [activeOrgId, _activeOrgSignal],
223
+ "/organization/activate",
224
+ $fetch,
225
+ () => ({
226
+ method: "POST",
227
+ credentials: "include",
228
+ body: {
229
+ orgId: activeOrgId.get()
230
+ }
231
+ })
232
+ );
233
+ return {
234
+ _listOrg,
235
+ _activeOrgSignal,
236
+ activeOrganization,
237
+ listOrganizations
238
+ };
239
+ },
240
+ atomListeners: [
241
+ {
242
+ matcher(path) {
243
+ return path === "/organization/create" || path === "/organization/delete";
244
+ },
245
+ signal: "_listOrg"
246
+ },
247
+ {
248
+ matcher(path) {
249
+ return path.startsWith("/organization");
250
+ },
251
+ signal: "_activeOrgSignal"
252
+ }
253
+ ]
254
+ };
255
+ };
256
+
257
+ // src/plugins/username/client.ts
258
+ var usernameClient = () => {
259
+ return {
260
+ id: "username",
261
+ $InferServerPlugin: {}
262
+ };
263
+ };
264
+
265
+ // src/plugins/passkey/client.ts
266
+ import {
267
+ WebAuthnError,
268
+ startAuthentication,
269
+ startRegistration
270
+ } from "@simplewebauthn/browser";
271
+
272
+ // src/utils/logger.ts
273
+ import { createConsola } from "consola";
274
+ var consola = createConsola({
275
+ formatOptions: {
276
+ date: false,
277
+ colors: true,
278
+ compact: true
279
+ },
280
+ defaults: {
281
+ tag: "Better Auth"
282
+ }
283
+ });
284
+ var createLogger = (options) => {
285
+ return {
286
+ log: (...args) => {
287
+ !options?.disabled && consola.log("", ...args);
288
+ },
289
+ error: (...args) => {
290
+ !options?.disabled && consola.error("", ...args);
291
+ },
292
+ warn: (...args) => {
293
+ !options?.disabled && consola.warn("", ...args);
294
+ },
295
+ info: (...args) => {
296
+ !options?.disabled && consola.info("", ...args);
297
+ },
298
+ debug: (...args) => {
299
+ !options?.disabled && consola.debug("", ...args);
300
+ },
301
+ box: (...args) => {
302
+ !options?.disabled && consola.box("", ...args);
303
+ },
304
+ success: (...args) => {
305
+ !options?.disabled && consola.success("", ...args);
306
+ },
307
+ break: (...args) => {
308
+ !options?.disabled && console.log("\n");
309
+ }
310
+ };
311
+ };
312
+ var logger = createLogger();
313
+
314
+ // src/plugins/passkey/client.ts
315
+ import { atom as atom4 } from "nanostores";
316
+ var getPasskeyActions = ($fetch, {
317
+ _listPasskeys
318
+ }) => {
319
+ const signInPasskey = async (opts, options) => {
320
+ const response = await $fetch(
321
+ "/passkey/generate-authenticate-options",
322
+ {
323
+ method: "POST",
324
+ body: {
325
+ email: opts?.email,
326
+ callbackURL: opts?.callbackURL
327
+ }
328
+ }
329
+ );
330
+ if (!response.data) {
331
+ return response;
332
+ }
333
+ try {
334
+ const res = await startAuthentication(
335
+ response.data,
336
+ opts?.autoFill || false
337
+ );
338
+ const verified = await $fetch("/passkey/verify-authentication", {
339
+ body: {
340
+ response: res
341
+ },
342
+ ...opts?.fetchOptions,
343
+ ...options
344
+ });
345
+ if (!verified.data) {
346
+ return verified;
347
+ }
348
+ } catch (e) {
349
+ console.log(e);
350
+ }
351
+ };
352
+ const registerPasskey = async (opts, fetchOpts) => {
353
+ const options = await $fetch(
354
+ "/passkey/generate-register-options",
355
+ {
356
+ method: "GET"
357
+ }
358
+ );
359
+ if (!options.data) {
360
+ return options;
361
+ }
362
+ try {
363
+ const res = await startRegistration(options.data);
364
+ const verified = await $fetch("/passkey/verify-registration", {
365
+ ...opts?.fetchOptions,
366
+ ...fetchOpts,
367
+ body: {
368
+ response: res,
369
+ name: opts?.name
370
+ }
371
+ });
372
+ if (!verified.data) {
373
+ return verified;
374
+ }
375
+ _listPasskeys.set(Math.random());
376
+ } catch (e) {
377
+ if (e instanceof WebAuthnError) {
378
+ if (e.code === "ERROR_AUTHENTICATOR_PREVIOUSLY_REGISTERED") {
379
+ return {
380
+ data: null,
381
+ error: {
382
+ message: "previously registered",
383
+ status: 400,
384
+ statusText: "BAD_REQUEST"
385
+ }
386
+ };
387
+ }
388
+ if (e.code === "ERROR_CEREMONY_ABORTED") {
389
+ return {
390
+ data: null,
391
+ error: {
392
+ message: "registration cancelled",
393
+ status: 400,
394
+ statusText: "BAD_REQUEST"
395
+ }
396
+ };
397
+ }
398
+ return {
399
+ data: null,
400
+ error: {
401
+ message: e.message,
402
+ status: 400,
403
+ statusText: "BAD_REQUEST"
404
+ }
405
+ };
406
+ }
407
+ return {
408
+ data: null,
409
+ error: {
410
+ message: e instanceof Error ? e.message : "unknown error",
411
+ status: 500,
412
+ statusText: "INTERNAL_SERVER_ERROR"
413
+ }
414
+ };
415
+ }
416
+ };
417
+ return {
418
+ signIn: {
419
+ /**
420
+ * Sign in with a registered passkey
421
+ */
422
+ passkey: signInPasskey
423
+ },
424
+ passkey: {
425
+ /**
426
+ * Add a passkey to the user account
427
+ */
428
+ addPasskey: registerPasskey
429
+ },
430
+ /**
431
+ * Inferred Internal Types
432
+ */
433
+ $Infer: {}
434
+ };
435
+ };
436
+ var passkeyClient = () => {
437
+ const _listPasskeys = atom4();
438
+ return {
439
+ id: "passkey",
440
+ $InferServerPlugin: {},
441
+ getActions: ($fetch) => getPasskeyActions($fetch, {
442
+ _listPasskeys
443
+ }),
444
+ getAtoms($fetch) {
445
+ const listPasskeys = useAuthQuery(
446
+ _listPasskeys,
447
+ "/passkey/list-user-passkeys",
448
+ $fetch,
449
+ {
450
+ method: "GET",
451
+ credentials: "include"
452
+ }
453
+ );
454
+ return {
455
+ listPasskeys,
456
+ _listPasskeys
457
+ };
458
+ },
459
+ pathMethods: {
460
+ "/passkey/register": "POST",
461
+ "/passkey/authenticate": "POST"
462
+ },
463
+ atomListeners: [
464
+ {
465
+ matcher(path) {
466
+ return path === "/passkey/verify-registration" || path === "/passkey/delete-passkey";
467
+ },
468
+ signal: "_listPasskeys"
469
+ }
470
+ ]
471
+ };
472
+ };
473
+
474
+ // src/plugins/two-factor/client.ts
475
+ var twoFactorClient = (options = {
476
+ redirect: true,
477
+ twoFactorPage: "/"
478
+ }) => {
479
+ return {
480
+ id: "two-factor",
481
+ $InferServerPlugin: {},
482
+ atomListeners: [
483
+ {
484
+ matcher: (path) => path === "/two-factor/enable" || path === "/two-factor/send-otp" || path === "/two-factor/disable",
485
+ signal: "_sessionSignal"
486
+ }
487
+ ],
488
+ pathMethods: {
489
+ "/two-factor/disable": "POST",
490
+ "/two-factor/enable": "POST",
491
+ "/two-factor/send-otp": "POST"
492
+ },
493
+ fetchPlugins: [
494
+ {
495
+ id: "two-factor",
496
+ name: "two-factor",
497
+ hooks: {
498
+ async onSuccess(context) {
499
+ if (context.data?.twoFactorRedirect) {
500
+ if (options.redirect || options.twoFactorPage) {
501
+ if (typeof window !== "undefined") {
502
+ window.location.href = options.twoFactorPage;
503
+ }
504
+ }
505
+ }
506
+ }
507
+ }
508
+ }
509
+ ]
510
+ };
511
+ };
512
+
513
+ // src/plugins/magic-link/client.ts
514
+ var magicLinkClient = () => {
515
+ return {
516
+ id: "magic-link",
517
+ $InferServerPlugin: {}
518
+ };
519
+ };
520
+ export {
521
+ getPasskeyActions,
522
+ magicLinkClient,
523
+ organizationClient,
524
+ passkeyClient,
525
+ twoFactorClient,
526
+ usernameClient
527
+ };