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 +23 -11
- package/src/client.ts +5 -16
- package/src/index.ts +72 -75
- package/src/types.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "better-near-auth",
|
|
3
|
-
"version": "0.3.
|
|
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
|
-
"
|
|
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.
|
|
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.2",
|
|
64
|
+
"nanostores": "^1.1.0",
|
|
65
|
+
"near-kit": "^0.7.0",
|
|
54
66
|
"near-sign-verify": "^0.4.5",
|
|
55
|
-
"zod": "^4.
|
|
67
|
+
"zod": "^4.3.5"
|
|
56
68
|
},
|
|
57
69
|
"devDependencies": {
|
|
58
70
|
"@types/bun": "latest",
|
|
59
|
-
"@types/node": "^
|
|
60
|
-
"better-auth": "^1.4.
|
|
71
|
+
"@types/node": "^25.0.9",
|
|
72
|
+
"better-auth": "^1.4.13",
|
|
61
73
|
"typescript": "^5.9.3",
|
|
62
|
-
"vitest": "^
|
|
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 {
|
|
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
|
|
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
|
-
|
|
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
|
-
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
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
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
|
-
|
|
383
|
+
const nonce = options.getNonce ? await options.getNonce() : generateNonce();
|
|
385
384
|
|
|
386
|
-
|
|
387
|
-
const nonceString = bytesToBase64(nonce);
|
|
385
|
+
const nonceString = bytesToBase64(nonce);
|
|
388
386
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
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
|
-
|
|
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
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
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
|
-
|
|
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);
|
|
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({
|