better-near-auth 0.3.2 → 0.3.3

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.
Files changed (2) hide show
  1. package/package.json +22 -10
  2. package/src/index.ts +78 -75
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.3",
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,21 +47,29 @@
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.1",
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.2.1"
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": "^22.19.3",
72
+ "better-auth": "^1.4.9",
61
73
  "typescript": "^5.9.3",
62
74
  "vitest": "^3.2.4"
63
75
  },
package/src/index.ts CHANGED
@@ -126,33 +126,34 @@ 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
+ // Verify the signature using near-sign-verify to extract publicKey
144
+ const result: VerificationResult = await verify(authToken, verifyOptions);
145
+
146
+ // Now retrieve the nonce using accountId, network, AND publicKey
147
+ const verification = await ctx.context.internalAdapter.findVerificationValue(
148
+ `siwn:${accountId}:${network}:${result.publicKey}`,
149
+ );
150
+
151
+ if (!verification || new Date() > verification.expiresAt) {
152
+ throw new APIError("UNAUTHORIZED", {
153
+ message: "Unauthorized: Invalid or expired nonce",
154
+ status: 401,
155
+ });
156
+ }
156
157
 
157
158
  if (result.accountId !== accountId) {
158
159
  throw new APIError("UNAUTHORIZED", {
@@ -370,30 +371,31 @@ export const siwn = (options: SIWNPluginOptions) =>
370
371
  method: "POST",
371
372
  body: NonceRequest,
372
373
  },
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
- }
374
+ async (ctx) => {
375
+ const { accountId, networkId, publicKey } = ctx.body;
376
+ const network = getNetworkFromAccountId(accountId);
377
+
378
+ if (networkId !== network) {
379
+ throw new APIError("BAD_REQUEST", {
380
+ message: "Network ID mismatch with account ID",
381
+ status: 400,
382
+ });
383
+ }
383
384
 
384
- const nonce = options.getNonce ? await options.getNonce() : generateNonce();
385
+ const nonce = options.getNonce ? await options.getNonce() : generateNonce();
385
386
 
386
- // Store nonce as base64 string for database compatibility
387
- const nonceString = bytesToBase64(nonce);
387
+ // Store nonce as base64 string for database compatibility
388
+ const nonceString = bytesToBase64(nonce);
388
389
 
389
- await ctx.context.internalAdapter.createVerificationValue({
390
- identifier: `siwn:${accountId}:${network}`,
391
- value: nonceString!,
392
- expiresAt: new Date(Date.now() + 15 * 60 * 1000),
393
- });
390
+ // Store nonce with accountId, network, and publicKey for unique identification
391
+ await ctx.context.internalAdapter.createVerificationValue({
392
+ identifier: `siwn:${accountId}:${network}:${publicKey}`,
393
+ value: nonceString!,
394
+ expiresAt: new Date(Date.now() + 15 * 60 * 1000),
395
+ });
394
396
 
395
- return ctx.json(NonceResponse.parse({ nonce: nonceString }));
396
- },
397
+ return ctx.json(NonceResponse.parse({ nonce: nonceString }));
398
+ },
397
399
  ),
398
400
  getSiwnProfile: createAuthEndpoint(
399
401
  "/near/profile",
@@ -463,35 +465,36 @@ export const siwn = (options: SIWNPluginOptions) =>
463
465
  });
464
466
  }
465
467
 
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
- }
468
+ try {
469
+ // First, verify the signature to get the publicKey
470
+ const requireFullAccessKey = options.requireFullAccessKey ?? true;
471
+ const verifyOptions: VerifyOptions = {
472
+ requireFullAccessKey: requireFullAccessKey,
473
+ ...(options.validateNonce
474
+ ? { validateNonce: options.validateNonce }
475
+ : { nonceMaxAge: 15 * 60 * 1000 }),
476
+ ...(options.validateRecipient
477
+ ? { validateRecipient: options.validateRecipient }
478
+ : { expectedRecipient: options.recipient }),
479
+ ...(options.validateMessage ? { validateMessage: options.validateMessage } : {}),
480
+ } as VerifyOptions;
481
+
482
+ // Verify the signature using near-sign-verify to extract publicKey
483
+ const result: VerificationResult = await verify(authToken, verifyOptions);
484
+
485
+ // Now retrieve the nonce using accountId, network, AND publicKey
486
+ const verification =
487
+ await ctx.context.internalAdapter.findVerificationValue(
488
+ `siwn:${accountId}:${network}:${result.publicKey}`,
489
+ );
479
490
 
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);
491
+ if (!verification || new Date() > verification.expiresAt) {
492
+ throw new APIError("UNAUTHORIZED", {
493
+ message: "Unauthorized: Invalid or expired nonce",
494
+ status: 401,
495
+ code: "UNAUTHORIZED_INVALID_OR_EXPIRED_NONCE",
496
+ });
497
+ }
495
498
 
496
499
  if (result.accountId !== accountId) {
497
500
  throw new APIError("UNAUTHORIZED", {