better-near-auth 0.3.1 → 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 (3) hide show
  1. package/package.json +23 -10
  2. package/src/client.ts +2 -3
  3. package/src/index.ts +80 -77
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "better-near-auth",
3
- "version": "0.3.1",
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,8 +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
+ "typecheck": "tsc --noEmit"
24
25
  },
26
+ "workspaces": [
27
+ "examples/browser-2-server",
28
+ "examples/browser-2-server/apps/*"
29
+ ],
25
30
  "keywords": [
26
31
  "better-auth",
27
32
  "near",
@@ -42,21 +47,29 @@
42
47
  "url": "https://github.com/elliotBraem/better-near-auth/issues"
43
48
  },
44
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
+ },
45
58
  "peerDependencies": {
46
- "better-auth": "^1.0.0",
47
- "typescript": "^5.7.0"
59
+ "better-auth": "^1.4.4",
60
+ "typescript": "^5.9.3"
48
61
  },
49
62
  "dependencies": {
50
- "@hot-labs/near-connect": "^latest",
51
- "nanostores": "^1.0.1",
52
- "near-kit": "^latest",
63
+ "@hot-labs/near-connect": "^0.8.1",
64
+ "nanostores": "^1.1.0",
65
+ "near-kit": "^0.7.0",
53
66
  "near-sign-verify": "^0.4.5",
54
- "zod": "^4.1.12"
67
+ "zod": "^4.2.1"
55
68
  },
56
69
  "devDependencies": {
57
70
  "@types/bun": "latest",
58
- "@types/node": "^24.9.2",
59
- "better-auth": "^1.4.4",
71
+ "@types/node": "^22.19.3",
72
+ "better-auth": "^1.4.9",
60
73
  "typescript": "^5.9.3",
61
74
  "vitest": "^3.2.4"
62
75
  },
package/src/client.ts CHANGED
@@ -151,7 +151,7 @@ export const siwnClient = (config: SIWNClientConfig): SIWNClientPlugin => {
151
151
  id: "siwn",
152
152
  $InferServerPlugin: {} as ReturnType<typeof siwn>,
153
153
 
154
- getAtoms: ($fetch) => ({
154
+ getAtoms: (_$fetch) => ({
155
155
  nearState,
156
156
  cachedNonce,
157
157
  }),
@@ -303,11 +303,10 @@ export const siwnClient = (config: SIWNClientConfig): SIWNClientPlugin => {
303
303
  },
304
304
  requestSignIn: {
305
305
  near: async (
306
- params: { recipient: string },
306
+ _params: { recipient: string },
307
307
  callbacks?: AuthCallbacks
308
308
  ): Promise<void> => {
309
309
  try {
310
- const { recipient } = params;
311
310
 
312
311
  clearNonce();
313
312
 
package/src/index.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { APIError, createAuthEndpoint, createAuthMiddleware, sessionMiddleware } from "better-auth/api";
2
2
  import { setSessionCookie } from "better-auth/cookies";
3
3
  import type { Account, BetterAuthPlugin, User } from "better-auth/types";
4
- import { generateNonce, parseAuthToken, verify, type VerificationResult, type VerifyOptions } from "near-sign-verify";
4
+ import { generateNonce, verify, type VerificationResult, type VerifyOptions } from "near-sign-verify";
5
5
  import { bytesToBase64 } from "./utils";
6
6
  import z from "zod";
7
7
  import { defaultGetProfile, getImageUrl, getNetworkFromAccountId } from "./profile";
@@ -115,7 +115,7 @@ export const siwn = (options: SIWNPluginOptions) =>
115
115
  requireRequest: true,
116
116
  },
117
117
  async (ctx) => {
118
- const { authToken, accountId, email } = ctx.body;
118
+ const { authToken, accountId } = ctx.body;
119
119
  const network = getNetworkFromAccountId(accountId);
120
120
  const session = ctx.context.session;
121
121
 
@@ -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, publicKey, 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", {