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.
- package/package.json +23 -10
- package/src/client.ts +2 -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.
|
|
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
|
-
"
|
|
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.
|
|
47
|
-
"typescript": "^5.
|
|
59
|
+
"better-auth": "^1.4.4",
|
|
60
|
+
"typescript": "^5.9.3"
|
|
48
61
|
},
|
|
49
62
|
"dependencies": {
|
|
50
|
-
"@hot-labs/near-connect": "^
|
|
51
|
-
"nanostores": "^1.0
|
|
52
|
-
"near-kit": "^
|
|
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
|
|
67
|
+
"zod": "^4.2.1"
|
|
55
68
|
},
|
|
56
69
|
"devDependencies": {
|
|
57
70
|
"@types/bun": "latest",
|
|
58
|
-
"@types/node": "^
|
|
59
|
-
"better-auth": "^1.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
|
-
|
|
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,
|
|
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
|
|
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
|
-
|
|
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", {
|