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.
- package/package.json +22 -10
- 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.
|
|
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
|
-
"
|
|
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.
|
|
48
|
-
"typescript": "^5.
|
|
59
|
+
"better-auth": "^1.4.4",
|
|
60
|
+
"typescript": "^5.9.3"
|
|
49
61
|
},
|
|
50
62
|
"dependencies": {
|
|
51
|
-
"@hot-labs/near-connect": "^
|
|
52
|
-
"nanostores": "^1.0
|
|
53
|
-
"near-kit": "^
|
|
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
|
|
67
|
+
"zod": "^4.2.1"
|
|
56
68
|
},
|
|
57
69
|
"devDependencies": {
|
|
58
70
|
"@types/bun": "latest",
|
|
59
|
-
"@types/node": "^
|
|
60
|
-
"better-auth": "^1.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
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
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
|
-
|
|
385
|
+
const nonce = options.getNonce ? await options.getNonce() : generateNonce();
|
|
385
386
|
|
|
386
|
-
|
|
387
|
-
|
|
387
|
+
// Store nonce as base64 string for database compatibility
|
|
388
|
+
const nonceString = bytesToBase64(nonce);
|
|
388
389
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
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
|
-
|
|
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
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
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
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
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", {
|