better-near-auth 0.3.2 → 0.3.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "better-near-auth",
3
- "version": "0.3.2",
3
+ "version": "0.3.4",
4
4
  "description": "Sign in with NEAR (SIWN) plugin for Better Auth",
5
5
  "main": "src/index.ts",
6
6
  "module": "src/index.ts",
@@ -20,9 +20,13 @@
20
20
  "test": "vitest",
21
21
  "test:watch": "vitest --watch",
22
22
  "lint": "tsc --noEmit",
23
- "dev": "bun run src/index.ts",
23
+ "start:b2s": "cd examples/browser-2-server && bun run dev",
24
24
  "typecheck": "tsc --noEmit"
25
25
  },
26
+ "workspaces": [
27
+ "examples/browser-2-server",
28
+ "examples/browser-2-server/apps/*"
29
+ ],
26
30
  "keywords": [
27
31
  "better-auth",
28
32
  "near",
@@ -43,23 +47,31 @@
43
47
  "url": "https://github.com/elliotBraem/better-near-auth/issues"
44
48
  },
45
49
  "homepage": "https://github.com/elliotBraem/better-near-auth#readme",
50
+ "catalog": {
51
+ "better-auth": "^1.4.4",
52
+ "typescript": "^5.9.3",
53
+ "zod": "^4.1.12",
54
+ "@types/node": "^22.17.2",
55
+ "@orpc/server": "^1.13.2",
56
+ "@orpc/client": "^1.13.2"
57
+ },
46
58
  "peerDependencies": {
47
- "better-auth": "^1.0.0",
48
- "typescript": "^5.7.0"
59
+ "better-auth": "^1.4.4",
60
+ "typescript": "^5.9.3"
49
61
  },
50
62
  "dependencies": {
51
- "@hot-labs/near-connect": "^latest",
52
- "nanostores": "^1.0.1",
53
- "near-kit": "^latest",
63
+ "@hot-labs/near-connect": "^0.8.2",
64
+ "nanostores": "^1.1.0",
65
+ "near-kit": "^0.7.0",
54
66
  "near-sign-verify": "^0.4.5",
55
- "zod": "^4.1.12"
67
+ "zod": "^4.3.5"
56
68
  },
57
69
  "devDependencies": {
58
70
  "@types/bun": "latest",
59
- "@types/node": "^24.9.2",
60
- "better-auth": "^1.4.4",
71
+ "@types/node": "^25.0.9",
72
+ "better-auth": "^1.4.13",
61
73
  "typescript": "^5.9.3",
62
- "vitest": "^3.2.4"
74
+ "vitest": "^4.0.17"
63
75
  },
64
76
  "files": [
65
77
  "src/",
package/src/client.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  import { NearConnector } from "@hot-labs/near-connect";
2
- import type { Account, NearWalletBase } from "@hot-labs/near-connect/build/types/wallet";
2
+ import type { NearWalletBase } from "@hot-labs/near-connect";
3
+
4
+ type Account = Awaited<ReturnType<NearWalletBase["getAccounts"]>>[number];
3
5
  import type { BetterAuthClientPlugin, BetterFetch, BetterFetchOption, BetterFetchResponse } from "better-auth/client";
4
6
  import { atom } from "nanostores";
5
7
  import { Near, fromHotConnect } from "near-kit";
@@ -22,7 +24,7 @@ export interface SIWNClientConfig {
22
24
  export interface CachedNonceData {
23
25
  nonce: string;
24
26
  accountId: string;
25
- publicKey: string;
27
+ publicKey?: string | null;
26
28
  networkId: string;
27
29
  timestamp: number;
28
30
  }
@@ -92,10 +94,9 @@ export const siwnClient = (config: SIWNClientConfig): SIWNClientPlugin => {
92
94
  const accountId = accounts?.[0]?.accountId;
93
95
  if (!accountId) return;
94
96
 
95
- // Create Near instance with Hot Connect wallet adapter
96
97
  nearInstance = new Near({
97
98
  network,
98
- wallet: fromHotConnect(connector),
99
+ wallet: fromHotConnect(connector as Parameters<typeof fromHotConnect>[0]),
99
100
  });
100
101
 
101
102
  // Update state with account info
@@ -217,10 +218,8 @@ export const siwnClient = (config: SIWNClientConfig): SIWNClientPlugin => {
217
218
  throw error;
218
219
  }
219
220
 
220
- // Get nonce first
221
221
  const nonceRequest: NonceRequestT = {
222
222
  accountId,
223
- publicKey: state.publicKey || "",
224
223
  networkId: (state.networkId || network) as "mainnet" | "testnet"
225
224
  };
226
225
 
@@ -330,18 +329,8 @@ export const siwnClient = (config: SIWNClientConfig): SIWNClientPlugin => {
330
329
 
331
330
  const { accountId, networkId, publicKey } = state;
332
331
 
333
- if (!publicKey) {
334
- throw new Error("Failed to get public key from wallet");
335
- }
336
-
337
- nearState.set({
338
- ...state,
339
- publicKey,
340
- });
341
-
342
332
  const nonceRequest: NonceRequestT = {
343
333
  accountId,
344
- publicKey: publicKey,
345
334
  networkId: networkId as "mainnet" | "testnet"
346
335
  };
347
336
 
package/src/index.ts CHANGED
@@ -126,33 +126,32 @@ export const siwn = (options: SIWNPluginOptions) =>
126
126
  });
127
127
  }
128
128
 
129
- try {
130
- const verification = await ctx.context.internalAdapter.findVerificationValue(
131
- `siwn:${accountId}:${network}`,
132
- );
133
-
134
- if (!verification || new Date() > verification.expiresAt) {
135
- throw new APIError("UNAUTHORIZED", {
136
- message: "Unauthorized: Invalid or expired nonce",
137
- status: 401,
138
- });
139
- }
140
-
141
- // Build verify options using plugin configuration
142
- const requireFullAccessKey = options.requireFullAccessKey ?? true;
143
- const verifyOptions: VerifyOptions = {
144
- requireFullAccessKey: requireFullAccessKey,
145
- ...(options.validateNonce
146
- ? { validateNonce: options.validateNonce }
147
- : { nonceMaxAge: 15 * 60 * 1000 }),
148
- ...(options.validateRecipient
149
- ? { validateRecipient: options.validateRecipient }
150
- : { expectedRecipient: options.recipient }),
151
- ...(options.validateMessage ? { validateMessage: options.validateMessage } : {}),
152
- } as VerifyOptions;
153
-
154
- // Verify the signature using near-sign-verify
155
- const result: VerificationResult = await verify(authToken, verifyOptions);
129
+ try {
130
+ // First, verify the signature to get the publicKey
131
+ const requireFullAccessKey = options.requireFullAccessKey ?? true;
132
+ const verifyOptions: VerifyOptions = {
133
+ requireFullAccessKey: requireFullAccessKey,
134
+ ...(options.validateNonce
135
+ ? { validateNonce: options.validateNonce }
136
+ : { nonceMaxAge: 15 * 60 * 1000 }),
137
+ ...(options.validateRecipient
138
+ ? { validateRecipient: options.validateRecipient }
139
+ : { expectedRecipient: options.recipient }),
140
+ ...(options.validateMessage ? { validateMessage: options.validateMessage } : {}),
141
+ } as VerifyOptions;
142
+
143
+ const result: VerificationResult = await verify(authToken, verifyOptions);
144
+
145
+ const verification = await ctx.context.internalAdapter.findVerificationValue(
146
+ `siwn:${accountId}:${network}`,
147
+ );
148
+
149
+ if (!verification || new Date() > verification.expiresAt) {
150
+ throw new APIError("UNAUTHORIZED", {
151
+ message: "Unauthorized: Invalid or expired nonce",
152
+ status: 401,
153
+ });
154
+ }
156
155
 
157
156
  if (result.accountId !== accountId) {
158
157
  throw new APIError("UNAUTHORIZED", {
@@ -370,30 +369,29 @@ export const siwn = (options: SIWNPluginOptions) =>
370
369
  method: "POST",
371
370
  body: NonceRequest,
372
371
  },
373
- async (ctx) => {
374
- const { accountId, networkId } = ctx.body;
375
- const network = getNetworkFromAccountId(accountId);
376
-
377
- if (networkId !== network) {
378
- throw new APIError("BAD_REQUEST", {
379
- message: "Network ID mismatch with account ID",
380
- status: 400,
381
- });
382
- }
372
+ async (ctx) => {
373
+ const { accountId, networkId } = ctx.body;
374
+ const network = getNetworkFromAccountId(accountId);
375
+
376
+ if (networkId !== network) {
377
+ throw new APIError("BAD_REQUEST", {
378
+ message: "Network ID mismatch with account ID",
379
+ status: 400,
380
+ });
381
+ }
383
382
 
384
- const nonce = options.getNonce ? await options.getNonce() : generateNonce();
383
+ const nonce = options.getNonce ? await options.getNonce() : generateNonce();
385
384
 
386
- // Store nonce as base64 string for database compatibility
387
- const nonceString = bytesToBase64(nonce);
385
+ const nonceString = bytesToBase64(nonce);
388
386
 
389
- await ctx.context.internalAdapter.createVerificationValue({
390
- identifier: `siwn:${accountId}:${network}`,
391
- value: nonceString!,
392
- expiresAt: new Date(Date.now() + 15 * 60 * 1000),
393
- });
387
+ await ctx.context.internalAdapter.createVerificationValue({
388
+ identifier: `siwn:${accountId}:${network}`,
389
+ value: nonceString!,
390
+ expiresAt: new Date(Date.now() + 15 * 60 * 1000),
391
+ });
394
392
 
395
- return ctx.json(NonceResponse.parse({ nonce: nonceString }));
396
- },
393
+ return ctx.json(NonceResponse.parse({ nonce: nonceString }));
394
+ },
397
395
  ),
398
396
  getSiwnProfile: createAuthEndpoint(
399
397
  "/near/profile",
@@ -463,35 +461,34 @@ export const siwn = (options: SIWNPluginOptions) =>
463
461
  });
464
462
  }
465
463
 
466
- try {
467
- const verification =
468
- await ctx.context.internalAdapter.findVerificationValue(
469
- `siwn:${accountId}:${network}`,
470
- );
471
-
472
- if (!verification || new Date() > verification.expiresAt) {
473
- throw new APIError("UNAUTHORIZED", {
474
- message: "Unauthorized: Invalid or expired nonce",
475
- status: 401,
476
- code: "UNAUTHORIZED_INVALID_OR_EXPIRED_NONCE",
477
- });
478
- }
464
+ try {
465
+ // First, verify the signature to get the publicKey
466
+ const requireFullAccessKey = options.requireFullAccessKey ?? true;
467
+ const verifyOptions: VerifyOptions = {
468
+ requireFullAccessKey: requireFullAccessKey,
469
+ ...(options.validateNonce
470
+ ? { validateNonce: options.validateNonce }
471
+ : { nonceMaxAge: 15 * 60 * 1000 }),
472
+ ...(options.validateRecipient
473
+ ? { validateRecipient: options.validateRecipient }
474
+ : { expectedRecipient: options.recipient }),
475
+ ...(options.validateMessage ? { validateMessage: options.validateMessage } : {}),
476
+ } as VerifyOptions;
477
+
478
+ const result: VerificationResult = await verify(authToken, verifyOptions);
479
+
480
+ const verification =
481
+ await ctx.context.internalAdapter.findVerificationValue(
482
+ `siwn:${accountId}:${network}`,
483
+ );
479
484
 
480
- // Build verify options using plugin configuration
481
- const requireFullAccessKey = options.requireFullAccessKey ?? true;
482
- const verifyOptions: VerifyOptions = {
483
- requireFullAccessKey: requireFullAccessKey,
484
- ...(options.validateNonce
485
- ? { validateNonce: options.validateNonce }
486
- : { nonceMaxAge: 15 * 60 * 1000 }),
487
- ...(options.validateRecipient
488
- ? { validateRecipient: options.validateRecipient }
489
- : { expectedRecipient: options.recipient }),
490
- ...(options.validateMessage ? { validateMessage: options.validateMessage } : {}),
491
- } as VerifyOptions;
492
-
493
- // Verify the signature using near-sign-verify
494
- const result: VerificationResult = await verify(authToken, verifyOptions);
485
+ if (!verification || new Date() > verification.expiresAt) {
486
+ throw new APIError("UNAUTHORIZED", {
487
+ message: "Unauthorized: Invalid or expired nonce",
488
+ status: 401,
489
+ code: "UNAUTHORIZED_INVALID_OR_EXPIRED_NONCE",
490
+ });
491
+ }
495
492
 
496
493
  if (result.accountId !== accountId) {
497
494
  throw new APIError("UNAUTHORIZED", {
package/src/types.ts CHANGED
@@ -36,7 +36,7 @@ export type Profile = z.infer<typeof profileSchema>;
36
36
 
37
37
  export const NonceRequest = z.object({
38
38
  accountId: accountIdSchema,
39
- publicKey: z.string(),
39
+ publicKey: z.string().optional(),
40
40
  networkId: z.union([z.literal("mainnet"), z.literal("testnet")])
41
41
  });
42
42
  export const VerifyRequest = z.object({