@bwalletx/connect 0.4.1 → 0.5.0
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/CHANGELOG.md +9 -0
- package/assets/protocol/401.jpg +0 -0
- package/assets/protocol/401.mp4 +0 -0
- package/assets/protocol/402.jpg +0 -0
- package/assets/protocol/402.mp4 +0 -0
- package/assets/protocol/403.jpg +0 -0
- package/assets/protocol/403.mp4 +0 -0
- package/dist/access-Z6xQwKVF.d.cts +118 -0
- package/dist/access-Z6xQwKVF.d.ts +118 -0
- package/dist/bwalletx-connect.global.js +17 -17
- package/dist/bwalletx-connect.global.js.map +1 -1
- package/dist/{chunk-NNMUSWPG.js → chunk-CVLXFNAG.js} +123 -1
- package/dist/chunk-CVLXFNAG.js.map +1 -0
- package/dist/{chunk-FUQ46RJV.js → chunk-GF6GUDZL.js} +2 -2
- package/dist/chunk-MWD4BCO5.js +182 -0
- package/dist/chunk-MWD4BCO5.js.map +1 -0
- package/dist/{chunk-7NAKOJGZ.js → chunk-QRXFL66W.js} +220 -10
- package/dist/chunk-QRXFL66W.js.map +1 -0
- package/dist/element.cjs +209 -0
- package/dist/element.cjs.map +1 -1
- package/dist/element.js +2 -1
- package/dist/element.js.map +1 -1
- package/dist/index.cjs +292 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +44 -1
- package/dist/index.d.ts +44 -1
- package/dist/index.js +30 -4
- package/dist/index.js.map +1 -1
- package/dist/next.cjs +377 -9
- package/dist/next.cjs.map +1 -1
- package/dist/next.d.cts +52 -2
- package/dist/next.d.ts +52 -2
- package/dist/next.js +92 -1
- package/dist/next.js.map +1 -1
- package/dist/react.cjs +209 -0
- package/dist/react.cjs.map +1 -1
- package/dist/react.js +3 -2
- package/dist/react.js.map +1 -1
- package/dist/server.cjs +406 -9
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +111 -1
- package/dist/server.d.ts +111 -1
- package/dist/server.js +47 -1
- package/package.json +10 -9
- package/dist/chunk-7NAKOJGZ.js.map +0 -1
- package/dist/chunk-NNMUSWPG.js.map +0 -1
- /package/dist/{chunk-FUQ46RJV.js.map → chunk-GF6GUDZL.js.map} +0 -0
package/dist/index.d.cts
CHANGED
|
@@ -2,6 +2,8 @@ import { WalletInterface, WalletProtocol } from '@bsv/sdk';
|
|
|
2
2
|
import { f as PairingOptions, W as WalletOption } from './wallets-VOKIKBbO.cjs';
|
|
3
3
|
export { g as BRC29_PROTOCOL, i as Brc29Remittance, B as BuyTokenRequest, C as ConnectedWallet, D as DiscoverWalletsOptions, H as HandCashAdapterOptions, L as LegacyPayer, j as PairConnectionOptions, k as PairStorage, l as PairedWallet, b as PayOptions, m as PayOutput, P as PayRequest, a as PayResult, n as PaySource, c as PaymentError, o as PaymentErrorCode, S as SocketFactory, q as SocketLike, T as TokenQuote, r as WalletKind, s as amountInSats, d as brc100Option, t as buyToken, u as clearPairing, e as discoverWallets, h as handcashAdapter, v as isIdentityKeyHex, w as isPaymail, x as pairBwalletX, p as pairingOption, y as pay, z as resolvePaymailP2P, A as restorePairing, E as tip } from './wallets-VOKIKBbO.cjs';
|
|
4
4
|
export { AuthorizeParams, GET_URL, SIGNIN_CSS, SIGNIN_TAG, SigninBrand, SigninLabel, SigninSize, SigninTheme, WEB_WALLET_URL, buildAuthorizeUrl, defineBwalletxSignin, markSvg, signinText } from './element.cjs';
|
|
5
|
+
import { b as AccessResponse, H as HoldingProof, A as AccessError } from './access-Z6xQwKVF.cjs';
|
|
6
|
+
export { c as ACCESS_ERROR_STATUS, d as ACCESS_PROTOCOL, e as ACCESS_VERSION, f as AccessChallenge, g as AccessErrorBody, h as AccessErrorCode, i as AccessKind, a as AccessRequirement, P as PROTOCOL_MEDIA, T as TokenRequirement, j as accessErrorMessage, k as canonicalJson, p as protocolMediaUrl } from './access-Z6xQwKVF.cjs';
|
|
5
7
|
|
|
6
8
|
/** Ids bWalletX announces in BRC-100 wallet discovery. */
|
|
7
9
|
declare const RDNS_EXTENSION = "com.bwalletx.extension";
|
|
@@ -107,4 +109,45 @@ declare function pickWallet(options: WalletOption[], o?: {
|
|
|
107
109
|
title?: string;
|
|
108
110
|
}): Promise<WalletOption | null>;
|
|
109
111
|
|
|
110
|
-
|
|
112
|
+
/**
|
|
113
|
+
* Browser side of the access protocol: ask the wallet to sign the site's challenge and prove the
|
|
114
|
+
* keys holding the required token, using standard BRC-100 calls only (so any BRC-100 wallet works).
|
|
115
|
+
*/
|
|
116
|
+
|
|
117
|
+
/** The challenge route's answer (createAccessChallenge). */
|
|
118
|
+
interface AccessChallengeLike {
|
|
119
|
+
challenge: string;
|
|
120
|
+
}
|
|
121
|
+
interface RequestAccessOptions {
|
|
122
|
+
/** Ask your server for a challenge. Gets the wallet's identity key. */
|
|
123
|
+
challenge: (identityKey: string) => Promise<AccessChallengeLike>;
|
|
124
|
+
/** Post the signed response to your server. Throw AccessError (or return an error body) to refuse. */
|
|
125
|
+
verify?: (response: AccessResponse) => Promise<unknown>;
|
|
126
|
+
wallet?: WalletInterface;
|
|
127
|
+
pairing?: SignInOptions['pairing'];
|
|
128
|
+
discoverMs?: number;
|
|
129
|
+
/** Basket holding BSV-21 outputs. Default 'bsv21' (bWalletX, 1Sat wallets). */
|
|
130
|
+
tokenBasket?: string;
|
|
131
|
+
}
|
|
132
|
+
interface RequestAccessResult {
|
|
133
|
+
response: AccessResponse;
|
|
134
|
+
/** Whatever `verify` returned. */
|
|
135
|
+
verified?: unknown;
|
|
136
|
+
wallet: WalletInterface;
|
|
137
|
+
}
|
|
138
|
+
/** Map a wallet / fetch failure to a protocol error. */
|
|
139
|
+
declare function toAccessError(e: unknown): AccessError;
|
|
140
|
+
/** Read an AccessErrorBody from a server answer and throw it as AccessError. */
|
|
141
|
+
declare function throwIfAccessError(body: unknown): void;
|
|
142
|
+
/**
|
|
143
|
+
* Sign the challenge with every key that locks an output of `tokenId` in the wallet's token basket.
|
|
144
|
+
* Outputs whose derivation can't be reproduced are skipped (the server counts only what is proven).
|
|
145
|
+
*/
|
|
146
|
+
declare function proveHoldings(wallet: WalletInterface, challenge: string, tokenId: string, basket?: string): Promise<HoldingProof[]>;
|
|
147
|
+
/**
|
|
148
|
+
* Run the whole flow: find the wallet, get a challenge, sign it, prove holdings, post to verify.
|
|
149
|
+
* Throws AccessError with a protocol code on every failure.
|
|
150
|
+
*/
|
|
151
|
+
declare function requestAccess(o: RequestAccessOptions): Promise<RequestAccessResult>;
|
|
152
|
+
|
|
153
|
+
export { type AccessChallengeLike, AccessError, AccessResponse, BWALLETX_RDNS, type ButtonOptions, type Challenge, type FoundBy, type FoundWallet, HoldingProof, LOGIN_PROTOCOL, LOGO_URL, type PairingModalOptions, PairingOptions, RDNS_EXTENSION, RDNS_MOBILE, type RequestAccessOptions, type RequestAccessResult, type SignInMethod, type SignInOptions, type SignInResult, WalletOption, connectBwalletX, discoverBwalletX, findBwalletX, inOrder, pairingModal, pickWallet, proveHoldings, qrSvg, renderButton, requestAccess, signInWithBwalletX, throwIfAccessError, toAccessError };
|
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,8 @@ import { WalletInterface, WalletProtocol } from '@bsv/sdk';
|
|
|
2
2
|
import { f as PairingOptions, W as WalletOption } from './wallets-VOKIKBbO.js';
|
|
3
3
|
export { g as BRC29_PROTOCOL, i as Brc29Remittance, B as BuyTokenRequest, C as ConnectedWallet, D as DiscoverWalletsOptions, H as HandCashAdapterOptions, L as LegacyPayer, j as PairConnectionOptions, k as PairStorage, l as PairedWallet, b as PayOptions, m as PayOutput, P as PayRequest, a as PayResult, n as PaySource, c as PaymentError, o as PaymentErrorCode, S as SocketFactory, q as SocketLike, T as TokenQuote, r as WalletKind, s as amountInSats, d as brc100Option, t as buyToken, u as clearPairing, e as discoverWallets, h as handcashAdapter, v as isIdentityKeyHex, w as isPaymail, x as pairBwalletX, p as pairingOption, y as pay, z as resolvePaymailP2P, A as restorePairing, E as tip } from './wallets-VOKIKBbO.js';
|
|
4
4
|
export { AuthorizeParams, GET_URL, SIGNIN_CSS, SIGNIN_TAG, SigninBrand, SigninLabel, SigninSize, SigninTheme, WEB_WALLET_URL, buildAuthorizeUrl, defineBwalletxSignin, markSvg, signinText } from './element.js';
|
|
5
|
+
import { b as AccessResponse, H as HoldingProof, A as AccessError } from './access-Z6xQwKVF.js';
|
|
6
|
+
export { c as ACCESS_ERROR_STATUS, d as ACCESS_PROTOCOL, e as ACCESS_VERSION, f as AccessChallenge, g as AccessErrorBody, h as AccessErrorCode, i as AccessKind, a as AccessRequirement, P as PROTOCOL_MEDIA, T as TokenRequirement, j as accessErrorMessage, k as canonicalJson, p as protocolMediaUrl } from './access-Z6xQwKVF.js';
|
|
5
7
|
|
|
6
8
|
/** Ids bWalletX announces in BRC-100 wallet discovery. */
|
|
7
9
|
declare const RDNS_EXTENSION = "com.bwalletx.extension";
|
|
@@ -107,4 +109,45 @@ declare function pickWallet(options: WalletOption[], o?: {
|
|
|
107
109
|
title?: string;
|
|
108
110
|
}): Promise<WalletOption | null>;
|
|
109
111
|
|
|
110
|
-
|
|
112
|
+
/**
|
|
113
|
+
* Browser side of the access protocol: ask the wallet to sign the site's challenge and prove the
|
|
114
|
+
* keys holding the required token, using standard BRC-100 calls only (so any BRC-100 wallet works).
|
|
115
|
+
*/
|
|
116
|
+
|
|
117
|
+
/** The challenge route's answer (createAccessChallenge). */
|
|
118
|
+
interface AccessChallengeLike {
|
|
119
|
+
challenge: string;
|
|
120
|
+
}
|
|
121
|
+
interface RequestAccessOptions {
|
|
122
|
+
/** Ask your server for a challenge. Gets the wallet's identity key. */
|
|
123
|
+
challenge: (identityKey: string) => Promise<AccessChallengeLike>;
|
|
124
|
+
/** Post the signed response to your server. Throw AccessError (or return an error body) to refuse. */
|
|
125
|
+
verify?: (response: AccessResponse) => Promise<unknown>;
|
|
126
|
+
wallet?: WalletInterface;
|
|
127
|
+
pairing?: SignInOptions['pairing'];
|
|
128
|
+
discoverMs?: number;
|
|
129
|
+
/** Basket holding BSV-21 outputs. Default 'bsv21' (bWalletX, 1Sat wallets). */
|
|
130
|
+
tokenBasket?: string;
|
|
131
|
+
}
|
|
132
|
+
interface RequestAccessResult {
|
|
133
|
+
response: AccessResponse;
|
|
134
|
+
/** Whatever `verify` returned. */
|
|
135
|
+
verified?: unknown;
|
|
136
|
+
wallet: WalletInterface;
|
|
137
|
+
}
|
|
138
|
+
/** Map a wallet / fetch failure to a protocol error. */
|
|
139
|
+
declare function toAccessError(e: unknown): AccessError;
|
|
140
|
+
/** Read an AccessErrorBody from a server answer and throw it as AccessError. */
|
|
141
|
+
declare function throwIfAccessError(body: unknown): void;
|
|
142
|
+
/**
|
|
143
|
+
* Sign the challenge with every key that locks an output of `tokenId` in the wallet's token basket.
|
|
144
|
+
* Outputs whose derivation can't be reproduced are skipped (the server counts only what is proven).
|
|
145
|
+
*/
|
|
146
|
+
declare function proveHoldings(wallet: WalletInterface, challenge: string, tokenId: string, basket?: string): Promise<HoldingProof[]>;
|
|
147
|
+
/**
|
|
148
|
+
* Run the whole flow: find the wallet, get a challenge, sign it, prove holdings, post to verify.
|
|
149
|
+
* Throws AccessError with a protocol code on every failure.
|
|
150
|
+
*/
|
|
151
|
+
declare function requestAccess(o: RequestAccessOptions): Promise<RequestAccessResult>;
|
|
152
|
+
|
|
153
|
+
export { type AccessChallengeLike, AccessError, AccessResponse, BWALLETX_RDNS, type ButtonOptions, type Challenge, type FoundBy, type FoundWallet, HoldingProof, LOGIN_PROTOCOL, LOGO_URL, type PairingModalOptions, PairingOptions, RDNS_EXTENSION, RDNS_MOBILE, type RequestAccessOptions, type RequestAccessResult, type SignInMethod, type SignInOptions, type SignInResult, WalletOption, connectBwalletX, discoverBwalletX, findBwalletX, inOrder, pairingModal, pickWallet, proveHoldings, qrSvg, renderButton, requestAccess, signInWithBwalletX, throwIfAccessError, toAccessError };
|
package/dist/index.js
CHANGED
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
qrSvg,
|
|
16
16
|
resolvePaymailP2P,
|
|
17
17
|
tip
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-GF6GUDZL.js";
|
|
19
19
|
import {
|
|
20
20
|
BWALLETX_RDNS,
|
|
21
21
|
GET_URL,
|
|
@@ -34,10 +34,24 @@ import {
|
|
|
34
34
|
inOrder,
|
|
35
35
|
markSvg,
|
|
36
36
|
pairBwalletX,
|
|
37
|
+
proveHoldings,
|
|
38
|
+
requestAccess,
|
|
37
39
|
restorePairing,
|
|
38
40
|
signInWithBwalletX,
|
|
39
|
-
signinText
|
|
40
|
-
|
|
41
|
+
signinText,
|
|
42
|
+
throwIfAccessError,
|
|
43
|
+
toAccessError
|
|
44
|
+
} from "./chunk-CVLXFNAG.js";
|
|
45
|
+
import {
|
|
46
|
+
ACCESS_ERROR_STATUS,
|
|
47
|
+
ACCESS_PROTOCOL,
|
|
48
|
+
ACCESS_VERSION,
|
|
49
|
+
AccessError,
|
|
50
|
+
PROTOCOL_MEDIA,
|
|
51
|
+
accessErrorMessage,
|
|
52
|
+
canonicalJson,
|
|
53
|
+
protocolMediaUrl
|
|
54
|
+
} from "./chunk-MWD4BCO5.js";
|
|
41
55
|
|
|
42
56
|
// src/button.ts
|
|
43
57
|
var CSS = `
|
|
@@ -83,21 +97,28 @@ function renderButton(el, opts = {}) {
|
|
|
83
97
|
return b;
|
|
84
98
|
}
|
|
85
99
|
export {
|
|
100
|
+
ACCESS_ERROR_STATUS,
|
|
101
|
+
ACCESS_PROTOCOL,
|
|
102
|
+
ACCESS_VERSION,
|
|
103
|
+
AccessError,
|
|
86
104
|
BRC29_PROTOCOL,
|
|
87
105
|
BWALLETX_RDNS,
|
|
88
106
|
GET_URL,
|
|
89
107
|
LOGIN_PROTOCOL,
|
|
90
108
|
LOGO_URL,
|
|
109
|
+
PROTOCOL_MEDIA,
|
|
91
110
|
PaymentError,
|
|
92
111
|
RDNS_EXTENSION,
|
|
93
112
|
RDNS_MOBILE,
|
|
94
113
|
SIGNIN_CSS,
|
|
95
114
|
SIGNIN_TAG,
|
|
96
115
|
WEB_WALLET_URL,
|
|
116
|
+
accessErrorMessage,
|
|
97
117
|
amountInSats,
|
|
98
118
|
brc100Option,
|
|
99
119
|
buildAuthorizeUrl,
|
|
100
120
|
buyToken,
|
|
121
|
+
canonicalJson,
|
|
101
122
|
clearPairing,
|
|
102
123
|
connectBwalletX,
|
|
103
124
|
defineBwalletxSignin,
|
|
@@ -114,12 +135,17 @@ export {
|
|
|
114
135
|
pairingOption,
|
|
115
136
|
pay,
|
|
116
137
|
pickWallet,
|
|
138
|
+
protocolMediaUrl,
|
|
139
|
+
proveHoldings,
|
|
117
140
|
qrSvg,
|
|
118
141
|
renderButton,
|
|
142
|
+
requestAccess,
|
|
119
143
|
resolvePaymailP2P,
|
|
120
144
|
restorePairing,
|
|
121
145
|
signInWithBwalletX,
|
|
122
146
|
signinText,
|
|
123
|
-
|
|
147
|
+
throwIfAccessError,
|
|
148
|
+
tip,
|
|
149
|
+
toAccessError
|
|
124
150
|
};
|
|
125
151
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/button.ts"],"sourcesContent":["const CSS = `\n.bwx-signin{display:inline-flex;align-items:center;gap:12px;min-height:48px;padding:10px 22px 10px 10px;border:0;border-radius:12px;cursor:pointer;background:linear-gradient(180deg,#fcd34d,#f59e0b);color:#000;text-align:left;font:700 16px/1.2 system-ui,-apple-system,\"Segoe UI\",sans-serif;box-shadow:0 8px 24px rgba(245,158,11,.2)}\n.bwx-signin:hover{background:linear-gradient(180deg,#fde68a,#fbbf24)}\n.bwx-signin:focus-visible{outline:2px solid #ffd24d;outline-offset:3px}\n.bwx-signin:disabled{opacity:.6;cursor:wait}\n.bwx-signin .bwx-mark{display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;border-radius:50%;background:#000;flex:none}\n.bwx-signin small{display:block;font-size:11px;font-weight:500;color:rgba(0,0,0,.6)}`;\n\nexport const LOGO_URL = 'https://bwalletx.com/logo.svg';\n\nexport interface ButtonOptions {\n /** Second line under the label. Default \"also works with bWallet\". false hides it. */\n subtitle?: string | false;\n onClick?: (button: HTMLButtonElement) => void;\n}\n\n/** Put the gold \"Sign in with bWalletX\" button into `el` (replacing its contents). */\nexport function renderButton(el: HTMLElement | string, opts: ButtonOptions = {}): HTMLButtonElement {\n const host = typeof el === 'string' ? document.querySelector<HTMLElement>(el) : el;\n if (!host) throw new Error(`renderButton: no element ${String(el)}`);\n if (!document.getElementById('bwx-signin-css')) {\n const style = document.createElement('style');\n style.id = 'bwx-signin-css';\n style.textContent = CSS;\n document.head.appendChild(style);\n }\n const b = document.createElement('button');\n b.type = 'button';\n b.className = 'bwx-signin';\n const mark = document.createElement('span');\n mark.className = 'bwx-mark';\n const img = document.createElement('img');\n img.src = LOGO_URL;\n img.alt = '';\n img.width = 15;\n img.height = 20;\n mark.appendChild(img);\n const label = document.createElement('span');\n label.className = 'bwx-label';\n label.textContent = 'Sign in with bWalletX';\n const sub = opts.subtitle === undefined ? 'also works with bWallet' : opts.subtitle;\n if (sub) {\n const small = document.createElement('small');\n small.textContent = sub;\n label.appendChild(small);\n }\n b.append(mark, label);\n if (opts.onClick) b.addEventListener('click', () => opts.onClick!(b));\n host.replaceChildren(b);\n return b;\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../src/button.ts"],"sourcesContent":["const CSS = `\n.bwx-signin{display:inline-flex;align-items:center;gap:12px;min-height:48px;padding:10px 22px 10px 10px;border:0;border-radius:12px;cursor:pointer;background:linear-gradient(180deg,#fcd34d,#f59e0b);color:#000;text-align:left;font:700 16px/1.2 system-ui,-apple-system,\"Segoe UI\",sans-serif;box-shadow:0 8px 24px rgba(245,158,11,.2)}\n.bwx-signin:hover{background:linear-gradient(180deg,#fde68a,#fbbf24)}\n.bwx-signin:focus-visible{outline:2px solid #ffd24d;outline-offset:3px}\n.bwx-signin:disabled{opacity:.6;cursor:wait}\n.bwx-signin .bwx-mark{display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;border-radius:50%;background:#000;flex:none}\n.bwx-signin small{display:block;font-size:11px;font-weight:500;color:rgba(0,0,0,.6)}`;\n\nexport const LOGO_URL = 'https://bwalletx.com/logo.svg';\n\nexport interface ButtonOptions {\n /** Second line under the label. Default \"also works with bWallet\". false hides it. */\n subtitle?: string | false;\n onClick?: (button: HTMLButtonElement) => void;\n}\n\n/** Put the gold \"Sign in with bWalletX\" button into `el` (replacing its contents). */\nexport function renderButton(el: HTMLElement | string, opts: ButtonOptions = {}): HTMLButtonElement {\n const host = typeof el === 'string' ? document.querySelector<HTMLElement>(el) : el;\n if (!host) throw new Error(`renderButton: no element ${String(el)}`);\n if (!document.getElementById('bwx-signin-css')) {\n const style = document.createElement('style');\n style.id = 'bwx-signin-css';\n style.textContent = CSS;\n document.head.appendChild(style);\n }\n const b = document.createElement('button');\n b.type = 'button';\n b.className = 'bwx-signin';\n const mark = document.createElement('span');\n mark.className = 'bwx-mark';\n const img = document.createElement('img');\n img.src = LOGO_URL;\n img.alt = '';\n img.width = 15;\n img.height = 20;\n mark.appendChild(img);\n const label = document.createElement('span');\n label.className = 'bwx-label';\n label.textContent = 'Sign in with bWalletX';\n const sub = opts.subtitle === undefined ? 'also works with bWallet' : opts.subtitle;\n if (sub) {\n const small = document.createElement('small');\n small.textContent = sub;\n label.appendChild(small);\n }\n b.append(mark, label);\n if (opts.onClick) b.addEventListener('click', () => opts.onClick!(b));\n host.replaceChildren(b);\n return b;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,IAAM,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAQL,IAAM,WAAW;AASjB,SAAS,aAAa,IAA0B,OAAsB,CAAC,GAAsB;AAClG,QAAM,OAAO,OAAO,OAAO,WAAW,SAAS,cAA2B,EAAE,IAAI;AAChF,MAAI,CAAC,KAAM,OAAM,IAAI,MAAM,4BAA4B,OAAO,EAAE,CAAC,EAAE;AACnE,MAAI,CAAC,SAAS,eAAe,gBAAgB,GAAG;AAC9C,UAAM,QAAQ,SAAS,cAAc,OAAO;AAC5C,UAAM,KAAK;AACX,UAAM,cAAc;AACpB,aAAS,KAAK,YAAY,KAAK;AAAA,EACjC;AACA,QAAM,IAAI,SAAS,cAAc,QAAQ;AACzC,IAAE,OAAO;AACT,IAAE,YAAY;AACd,QAAM,OAAO,SAAS,cAAc,MAAM;AAC1C,OAAK,YAAY;AACjB,QAAM,MAAM,SAAS,cAAc,KAAK;AACxC,MAAI,MAAM;AACV,MAAI,MAAM;AACV,MAAI,QAAQ;AACZ,MAAI,SAAS;AACb,OAAK,YAAY,GAAG;AACpB,QAAM,QAAQ,SAAS,cAAc,MAAM;AAC3C,QAAM,YAAY;AAClB,QAAM,cAAc;AACpB,QAAM,MAAM,KAAK,aAAa,SAAY,4BAA4B,KAAK;AAC3E,MAAI,KAAK;AACP,UAAM,QAAQ,SAAS,cAAc,OAAO;AAC5C,UAAM,cAAc;AACpB,UAAM,YAAY,KAAK;AAAA,EACzB;AACA,IAAE,OAAO,MAAM,KAAK;AACpB,MAAI,KAAK,QAAS,GAAE,iBAAiB,SAAS,MAAM,KAAK,QAAS,CAAC,CAAC;AACpE,OAAK,gBAAgB,CAAC;AACtB,SAAO;AACT;","names":[]}
|
package/dist/next.cjs
CHANGED
|
@@ -31,6 +31,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
31
31
|
var next_exports = {};
|
|
32
32
|
__export(next_exports, {
|
|
33
33
|
IdentityLinkConflictError: () => IdentityLinkConflictError,
|
|
34
|
+
createBwalletXAccess: () => createBwalletXAccess,
|
|
34
35
|
createBwalletXAuth: () => createBwalletXAuth,
|
|
35
36
|
linkIdentity: () => linkIdentity,
|
|
36
37
|
memoryStore: () => memoryStore,
|
|
@@ -78,7 +79,7 @@ async function signInOrLink(identityKey, o) {
|
|
|
78
79
|
}
|
|
79
80
|
|
|
80
81
|
// src/server.ts
|
|
81
|
-
var
|
|
82
|
+
var import_sdk2 = require("@bsv/sdk");
|
|
82
83
|
|
|
83
84
|
// src/nonce-stores.ts
|
|
84
85
|
var ttlSeconds = (c) => Math.max(1, Math.ceil((c.expiresAt - Date.now()) / 1e3));
|
|
@@ -213,6 +214,292 @@ function readCookie(header, name) {
|
|
|
213
214
|
return null;
|
|
214
215
|
}
|
|
215
216
|
|
|
217
|
+
// src/access.ts
|
|
218
|
+
var ACCESS_PROTOCOL = [2, "bwalletx access"];
|
|
219
|
+
var ACCESS_CHALLENGE_LIFETIME_S = 120;
|
|
220
|
+
var MAX_HOLDING_PROOFS = 50;
|
|
221
|
+
var ACCESS_ERROR_STATUS = {
|
|
222
|
+
not_installed: 424,
|
|
223
|
+
user_rejected: 403,
|
|
224
|
+
wrong_account: 409,
|
|
225
|
+
insufficient_tokens: 403,
|
|
226
|
+
identity_level_too_low: 403,
|
|
227
|
+
kyc_required: 403,
|
|
228
|
+
indexer_unavailable: 503,
|
|
229
|
+
expired: 401,
|
|
230
|
+
replay: 401,
|
|
231
|
+
invalid_signature: 401,
|
|
232
|
+
bad_request: 400
|
|
233
|
+
};
|
|
234
|
+
var AccessError = class extends Error {
|
|
235
|
+
code;
|
|
236
|
+
have;
|
|
237
|
+
need;
|
|
238
|
+
constructor(code, message, extra = {}) {
|
|
239
|
+
super(message ?? code);
|
|
240
|
+
this.name = "AccessError";
|
|
241
|
+
this.code = code;
|
|
242
|
+
this.have = extra.have;
|
|
243
|
+
this.need = extra.need;
|
|
244
|
+
}
|
|
245
|
+
toJSON() {
|
|
246
|
+
const b = { error: this.code, message: this.message };
|
|
247
|
+
if (this.have !== void 0) b.have = this.have;
|
|
248
|
+
if (this.need !== void 0) b.need = this.need;
|
|
249
|
+
return b;
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
function canonicalJson(v) {
|
|
253
|
+
if (v === null) return "null";
|
|
254
|
+
if (typeof v === "string" || typeof v === "boolean") return JSON.stringify(v);
|
|
255
|
+
if (typeof v === "number") {
|
|
256
|
+
if (!Number.isFinite(v)) throw new Error("canonicalJson: non-finite number");
|
|
257
|
+
return JSON.stringify(v);
|
|
258
|
+
}
|
|
259
|
+
if (Array.isArray(v)) return `[${v.map((x) => canonicalJson(x)).join(",")}]`;
|
|
260
|
+
if (typeof v === "object") {
|
|
261
|
+
const o = v;
|
|
262
|
+
const keys2 = Object.keys(o).filter((k) => o[k] !== void 0).sort();
|
|
263
|
+
return `{${keys2.map((k) => `${JSON.stringify(k)}:${canonicalJson(o[k])}`).join(",")}}`;
|
|
264
|
+
}
|
|
265
|
+
throw new Error(`canonicalJson: unsupported ${typeof v}`);
|
|
266
|
+
}
|
|
267
|
+
function challengeBytes(challenge) {
|
|
268
|
+
return Array.from(new TextEncoder().encode(challenge));
|
|
269
|
+
}
|
|
270
|
+
var TOKEN_ID = /^bsv21:[0-9a-f]{64}_\d{1,6}$/;
|
|
271
|
+
var RAW = /^[1-9]\d{0,38}$/;
|
|
272
|
+
function normaliseRequirement(r) {
|
|
273
|
+
if (!r || typeof r !== "object") throw new AccessError("bad_request", "require must be an object");
|
|
274
|
+
const req = r;
|
|
275
|
+
const out = {};
|
|
276
|
+
if (req.token !== void 0) {
|
|
277
|
+
const t = req.token;
|
|
278
|
+
if (!t || typeof t !== "object" || typeof t.id !== "string") throw new AccessError("bad_request", "require.token.id is required");
|
|
279
|
+
const id = t.id.trim().toLowerCase();
|
|
280
|
+
if (!TOKEN_ID.test(id)) throw new AccessError("bad_request", "require.token.id must be 'bsv21:<txid>_<vout>'");
|
|
281
|
+
const hasMin = t.min !== void 0;
|
|
282
|
+
const hasBps = t.minBps !== void 0;
|
|
283
|
+
if (hasMin === hasBps) throw new AccessError("bad_request", "require.token needs exactly one of min or minBps");
|
|
284
|
+
out.token = { id };
|
|
285
|
+
if (hasMin) {
|
|
286
|
+
if (typeof t.min !== "string" || !RAW.test(t.min)) throw new AccessError("bad_request", "require.token.min must be a positive integer string (raw units)");
|
|
287
|
+
out.token.min = t.min;
|
|
288
|
+
} else {
|
|
289
|
+
if (!Number.isInteger(t.minBps) || t.minBps < 1 || t.minBps > 1e4) {
|
|
290
|
+
throw new AccessError("bad_request", "require.token.minBps must be an integer 1..10000");
|
|
291
|
+
}
|
|
292
|
+
out.token.minBps = t.minBps;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
if (req.identityLevel !== void 0) {
|
|
296
|
+
if (!Number.isInteger(req.identityLevel) || req.identityLevel < 1 || req.identityLevel > 10) {
|
|
297
|
+
throw new AccessError("bad_request", "require.identityLevel must be an integer 1..10");
|
|
298
|
+
}
|
|
299
|
+
out.identityLevel = req.identityLevel;
|
|
300
|
+
}
|
|
301
|
+
if (req.kyc !== void 0) {
|
|
302
|
+
if (typeof req.kyc !== "boolean") throw new AccessError("bad_request", "require.kyc must be a boolean");
|
|
303
|
+
if (req.kyc) out.kyc = true;
|
|
304
|
+
}
|
|
305
|
+
if (!out.token && !out.identityLevel && !out.kyc) throw new AccessError("bad_request", "require is empty");
|
|
306
|
+
return out;
|
|
307
|
+
}
|
|
308
|
+
function kindFor(r) {
|
|
309
|
+
return r.token ? "403" : "401";
|
|
310
|
+
}
|
|
311
|
+
function parseAccessChallenge(s) {
|
|
312
|
+
if (typeof s !== "string" || s.length > 4096) throw new AccessError("bad_request", "challenge must be a string");
|
|
313
|
+
let c;
|
|
314
|
+
try {
|
|
315
|
+
c = JSON.parse(s);
|
|
316
|
+
} catch {
|
|
317
|
+
throw new AccessError("bad_request", "challenge is not JSON");
|
|
318
|
+
}
|
|
319
|
+
if (!c || typeof c !== "object" || c.v !== 1) throw new AccessError("bad_request", "unsupported challenge version");
|
|
320
|
+
if (c.kind !== "401" && c.kind !== "402" && c.kind !== "403") throw new AccessError("bad_request", "bad kind");
|
|
321
|
+
if (typeof c.origin !== "string" || typeof c.nonce !== "string" || !/^[A-Za-z0-9_-]{16,64}$/.test(c.nonce)) {
|
|
322
|
+
throw new AccessError("bad_request", "bad origin or nonce");
|
|
323
|
+
}
|
|
324
|
+
if (!Number.isInteger(c.iat) || !Number.isInteger(c.exp) || c.exp <= c.iat) throw new AccessError("bad_request", "bad iat/exp");
|
|
325
|
+
if (c.sub !== void 0 && (typeof c.sub !== "string" || !/^0[23][0-9a-f]{64}$/.test(c.sub))) throw new AccessError("bad_request", "bad sub");
|
|
326
|
+
c.require = normaliseRequirement(c.require);
|
|
327
|
+
if (canonicalJson(c) !== s) throw new AccessError("bad_request", "challenge is not canonical");
|
|
328
|
+
return c;
|
|
329
|
+
}
|
|
330
|
+
function tokenNeedRaw(t, supplyRaw) {
|
|
331
|
+
if (t.min) return t.min;
|
|
332
|
+
if (supplyRaw === void 0 || !/^\d+$/.test(supplyRaw)) throw new AccessError("indexer_unavailable", "token supply unknown");
|
|
333
|
+
const n = BigInt(supplyRaw) * BigInt(t.minBps);
|
|
334
|
+
const need = (n + 9999n) / 10000n;
|
|
335
|
+
return (need < 1n ? 1n : need).toString();
|
|
336
|
+
}
|
|
337
|
+
function atLeastRaw(have, need) {
|
|
338
|
+
try {
|
|
339
|
+
return BigInt(have) >= BigInt(need);
|
|
340
|
+
} catch {
|
|
341
|
+
return false;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
function accessErrorMessage(code, e = {}) {
|
|
345
|
+
switch (code) {
|
|
346
|
+
case "not_installed":
|
|
347
|
+
return "No bWalletX (or other BRC-100 wallet) was found.";
|
|
348
|
+
case "user_rejected":
|
|
349
|
+
return "The request was declined in the wallet.";
|
|
350
|
+
case "wrong_account":
|
|
351
|
+
return "The wallet is signed in to a different account than this site expects.";
|
|
352
|
+
case "insufficient_tokens":
|
|
353
|
+
return `Not enough tokens: holding ${e.have ?? 0}, need ${e.need ?? "?"}.`;
|
|
354
|
+
case "identity_level_too_low":
|
|
355
|
+
return `Identity level ${e.have ?? 0} is below the required level ${e.need ?? "?"}.`;
|
|
356
|
+
case "kyc_required":
|
|
357
|
+
return "A verified identity (KYC) is required.";
|
|
358
|
+
case "indexer_unavailable":
|
|
359
|
+
return "Holdings could not be checked right now. Try again shortly.";
|
|
360
|
+
case "expired":
|
|
361
|
+
return "The request expired. Try again.";
|
|
362
|
+
case "replay":
|
|
363
|
+
return "That request was already used. Try again.";
|
|
364
|
+
case "invalid_signature":
|
|
365
|
+
return "The wallet signature did not verify.";
|
|
366
|
+
case "bad_request":
|
|
367
|
+
return "The request was malformed.";
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// src/access-server.ts
|
|
372
|
+
var import_sdk = require("@bsv/sdk");
|
|
373
|
+
function normalOrigin(origin) {
|
|
374
|
+
const u = new URL(origin);
|
|
375
|
+
if (u.origin === "null" || u.origin !== origin.replace(/\/$/, "")) throw new Error(`origin must be scheme://host[:port], got ${origin}`);
|
|
376
|
+
return u.origin;
|
|
377
|
+
}
|
|
378
|
+
function randomNonce() {
|
|
379
|
+
const b = new Uint8Array(18);
|
|
380
|
+
globalThis.crypto.getRandomValues(b);
|
|
381
|
+
let s = "";
|
|
382
|
+
for (const x of b) s += String.fromCharCode(x);
|
|
383
|
+
return btoa(s).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
384
|
+
}
|
|
385
|
+
async function createAccessChallenge(o) {
|
|
386
|
+
const origin = normalOrigin(o.origin);
|
|
387
|
+
const require2 = normaliseRequirement(o.require);
|
|
388
|
+
let sub;
|
|
389
|
+
if (o.identityKey !== void 0 && o.identityKey !== null && o.identityKey !== "") {
|
|
390
|
+
if (!isIdentityKey(o.identityKey)) throw new AccessError("bad_request", "identityKey must be a compressed public key");
|
|
391
|
+
sub = o.identityKey.toLowerCase();
|
|
392
|
+
}
|
|
393
|
+
const iat = Math.floor((o.now ?? Date.now()) / 1e3);
|
|
394
|
+
const exp = iat + (o.lifetimeS ?? ACCESS_CHALLENGE_LIFETIME_S);
|
|
395
|
+
const nonce = randomNonce();
|
|
396
|
+
const c = { v: 1, kind: o.kind ?? kindFor(require2), origin, nonce, iat, exp, sub, require: require2 };
|
|
397
|
+
const challenge = canonicalJson(c);
|
|
398
|
+
await o.store.put(nonce, { identityKey: sub ?? "", origin, message: challenge, expiresAt: exp * 1e3 });
|
|
399
|
+
return { challenge, nonce, kind: c.kind, protocolID: ACCESS_PROTOCOL, keyID: nonce, exp };
|
|
400
|
+
}
|
|
401
|
+
var isByteArray = (s) => Array.isArray(s) && s.length >= 8 && s.length <= 80 && s.every((b) => Number.isInteger(b) && b >= 0 && b <= 255);
|
|
402
|
+
function verifyHoldingProof(challenge, p) {
|
|
403
|
+
const proof = p;
|
|
404
|
+
if (!proof || typeof proof.pubkey !== "string" || !/^0[23][0-9a-fA-F]{64}$/.test(proof.pubkey) || !isByteArray(proof.signature)) return null;
|
|
405
|
+
try {
|
|
406
|
+
const pub = import_sdk.PublicKey.fromString(proof.pubkey);
|
|
407
|
+
const sig = import_sdk.Signature.fromDER(proof.signature);
|
|
408
|
+
return pub.verify(challengeBytes(challenge), sig) ? pub.toAddress() : null;
|
|
409
|
+
} catch {
|
|
410
|
+
return null;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
async function verifyAccess(o) {
|
|
414
|
+
const r = o.response;
|
|
415
|
+
if (!r || typeof r !== "object" || r.v !== 1) throw new AccessError("bad_request", "response must be { v: 1, challenge, identityKey, signature }");
|
|
416
|
+
const c = parseAccessChallenge(r.challenge);
|
|
417
|
+
const nowS = Math.floor((o.now ?? Date.now()) / 1e3);
|
|
418
|
+
const pending = await o.store.take(c.nonce);
|
|
419
|
+
if (!pending) throw new AccessError(nowS >= c.exp ? "expired" : "replay");
|
|
420
|
+
if (nowS >= c.exp || (o.now ?? Date.now()) > pending.expiresAt) throw new AccessError("expired");
|
|
421
|
+
if (pending.message !== r.challenge) throw new AccessError("replay", "challenge does not match the one issued");
|
|
422
|
+
let origin;
|
|
423
|
+
try {
|
|
424
|
+
origin = normalOrigin(o.origin);
|
|
425
|
+
} catch {
|
|
426
|
+
throw new AccessError("bad_request", "server origin is misconfigured");
|
|
427
|
+
}
|
|
428
|
+
if (c.origin !== origin || pending.origin !== origin) throw new AccessError("bad_request", "origin mismatch");
|
|
429
|
+
if (o.require && canonicalJson(normaliseRequirement(o.require)) !== canonicalJson(c.require)) {
|
|
430
|
+
throw new AccessError("bad_request", "challenge requirement does not match this gate");
|
|
431
|
+
}
|
|
432
|
+
if (!isIdentityKey(r.identityKey)) throw new AccessError("bad_request", "identityKey must be a compressed public key");
|
|
433
|
+
const identityKey = r.identityKey.toLowerCase();
|
|
434
|
+
if (c.sub && c.sub !== identityKey) throw new AccessError("wrong_account");
|
|
435
|
+
if (!isByteArray(r.signature)) throw new AccessError("invalid_signature");
|
|
436
|
+
let valid = false;
|
|
437
|
+
try {
|
|
438
|
+
({ valid } = await new import_sdk.ProtoWallet("anyone").verifySignature({
|
|
439
|
+
protocolID: ACCESS_PROTOCOL,
|
|
440
|
+
keyID: c.nonce,
|
|
441
|
+
counterparty: identityKey,
|
|
442
|
+
data: challengeBytes(r.challenge),
|
|
443
|
+
signature: r.signature
|
|
444
|
+
}));
|
|
445
|
+
} catch {
|
|
446
|
+
valid = false;
|
|
447
|
+
}
|
|
448
|
+
if (!valid) throw new AccessError("invalid_signature");
|
|
449
|
+
const grant = { ok: true, identityKey, kind: c.kind, require: c.require, addresses: [], have: {} };
|
|
450
|
+
if (c.require.identityLevel || c.require.kyc) {
|
|
451
|
+
if (!o.resolver.identity) throw new AccessError("indexer_unavailable", "no identity resolver configured");
|
|
452
|
+
let id;
|
|
453
|
+
try {
|
|
454
|
+
id = await o.resolver.identity(identityKey, r);
|
|
455
|
+
} catch (e) {
|
|
456
|
+
throw new AccessError("indexer_unavailable", `identity lookup failed: ${e.message}`);
|
|
457
|
+
}
|
|
458
|
+
grant.have.identityLevel = id.level;
|
|
459
|
+
grant.have.kyc = id.kyc;
|
|
460
|
+
if (c.require.kyc && !id.kyc) throw new AccessError("kyc_required");
|
|
461
|
+
if (c.require.identityLevel && id.level < c.require.identityLevel) {
|
|
462
|
+
throw new AccessError("identity_level_too_low", void 0, { have: id.level, need: c.require.identityLevel });
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
if (c.require.token) {
|
|
466
|
+
const proofs = Array.isArray(r.holdings) ? r.holdings.slice(0, MAX_HOLDING_PROOFS) : [];
|
|
467
|
+
const addresses = /* @__PURE__ */ new Set();
|
|
468
|
+
for (const p of proofs) {
|
|
469
|
+
const a = verifyHoldingProof(r.challenge, p);
|
|
470
|
+
if (a) addresses.add(a);
|
|
471
|
+
}
|
|
472
|
+
grant.addresses = [...addresses].sort();
|
|
473
|
+
const check = await checkTokenHolding(c.require, grant.addresses, o.resolver);
|
|
474
|
+
grant.have.tokenRaw = check.have;
|
|
475
|
+
grant.have.needRaw = check.need;
|
|
476
|
+
}
|
|
477
|
+
return grant;
|
|
478
|
+
}
|
|
479
|
+
async function checkTokenHolding(require2, addresses, resolver) {
|
|
480
|
+
const t = require2.token;
|
|
481
|
+
if (!t) return { have: "0", need: "0" };
|
|
482
|
+
let supply;
|
|
483
|
+
if (t.minBps) {
|
|
484
|
+
if (!resolver.tokenSupply) throw new AccessError("indexer_unavailable", "no supply resolver configured");
|
|
485
|
+
try {
|
|
486
|
+
supply = await resolver.tokenSupply(t.id);
|
|
487
|
+
} catch (e) {
|
|
488
|
+
throw new AccessError("indexer_unavailable", `supply lookup failed: ${e.message}`);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
const need = tokenNeedRaw(t, supply);
|
|
492
|
+
if (!addresses.length) throw new AccessError("insufficient_tokens", void 0, { have: "0", need });
|
|
493
|
+
let have;
|
|
494
|
+
try {
|
|
495
|
+
have = await resolver.tokenBalance(t.id, addresses);
|
|
496
|
+
} catch (e) {
|
|
497
|
+
throw new AccessError("indexer_unavailable", `balance lookup failed: ${e.message}`);
|
|
498
|
+
}
|
|
499
|
+
if (!atLeastRaw(have, need)) throw new AccessError("insufficient_tokens", void 0, { have, need });
|
|
500
|
+
return { have, need };
|
|
501
|
+
}
|
|
502
|
+
|
|
216
503
|
// src/server.ts
|
|
217
504
|
var LOGIN_PROTOCOL = [2, "bwallet sign in"];
|
|
218
505
|
var CHALLENGE_LIFETIME_MS = 2 * 60 * 1e3;
|
|
@@ -244,13 +531,13 @@ var storeOrDefault = (s) => s ?? (defaultStore ??= memoryStore());
|
|
|
244
531
|
function isIdentityKey(x) {
|
|
245
532
|
if (typeof x !== "string" || !/^0[23][0-9a-fA-F]{64}$/.test(x)) return false;
|
|
246
533
|
try {
|
|
247
|
-
|
|
534
|
+
import_sdk2.PublicKey.fromString(x);
|
|
248
535
|
return true;
|
|
249
536
|
} catch {
|
|
250
537
|
return false;
|
|
251
538
|
}
|
|
252
539
|
}
|
|
253
|
-
function
|
|
540
|
+
function normalOrigin2(origin) {
|
|
254
541
|
const u = new URL(origin);
|
|
255
542
|
if (u.origin === "null" || u.origin !== origin.replace(/\/$/, "")) {
|
|
256
543
|
throw new Error(`origin must be scheme://host[:port], got ${origin}`);
|
|
@@ -268,7 +555,7 @@ function loginMessage(origin, nonce, expiresAt) {
|
|
|
268
555
|
`Expires: ${new Date(expiresAt).toISOString()}`
|
|
269
556
|
].join("\n");
|
|
270
557
|
}
|
|
271
|
-
function
|
|
558
|
+
function randomNonce2() {
|
|
272
559
|
const b = new Uint8Array(16);
|
|
273
560
|
globalThis.crypto.getRandomValues(b);
|
|
274
561
|
let s = "";
|
|
@@ -277,8 +564,8 @@ function randomNonce() {
|
|
|
277
564
|
}
|
|
278
565
|
async function createChallenge(opts) {
|
|
279
566
|
if (!isIdentityKey(opts.identityKey)) throw new Error("identityKey must be a compressed public key (66 hex chars)");
|
|
280
|
-
const origin =
|
|
281
|
-
const nonce =
|
|
567
|
+
const origin = normalOrigin2(opts.origin);
|
|
568
|
+
const nonce = randomNonce2();
|
|
282
569
|
const expiresAt = Date.now() + CHALLENGE_LIFETIME_MS;
|
|
283
570
|
const message = loginMessage(origin, nonce, expiresAt);
|
|
284
571
|
await storeOrDefault(opts.store).put(nonce, { identityKey: opts.identityKey.toLowerCase(), origin, message, expiresAt });
|
|
@@ -290,7 +577,7 @@ async function verifySignIn(opts) {
|
|
|
290
577
|
if (!c || Date.now() > c.expiresAt) return null;
|
|
291
578
|
let origin;
|
|
292
579
|
try {
|
|
293
|
-
origin =
|
|
580
|
+
origin = normalOrigin2(opts.origin);
|
|
294
581
|
} catch {
|
|
295
582
|
return null;
|
|
296
583
|
}
|
|
@@ -303,7 +590,7 @@ async function verifySignIn(opts) {
|
|
|
303
590
|
return null;
|
|
304
591
|
}
|
|
305
592
|
try {
|
|
306
|
-
const { valid } = await new
|
|
593
|
+
const { valid } = await new import_sdk2.ProtoWallet("anyone").verifySignature({
|
|
307
594
|
protocolID: LOGIN_PROTOCOL,
|
|
308
595
|
keyID: opts.nonce,
|
|
309
596
|
counterparty: identityKey,
|
|
@@ -317,7 +604,7 @@ async function verifySignIn(opts) {
|
|
|
317
604
|
}
|
|
318
605
|
function originAllowed(requestOrigin, origin) {
|
|
319
606
|
try {
|
|
320
|
-
return !!requestOrigin && requestOrigin ===
|
|
607
|
+
return !!requestOrigin && requestOrigin === normalOrigin2(origin);
|
|
321
608
|
} catch {
|
|
322
609
|
return false;
|
|
323
610
|
}
|
|
@@ -401,9 +688,90 @@ function createBwalletXAuth(o) {
|
|
|
401
688
|
};
|
|
402
689
|
return auth;
|
|
403
690
|
}
|
|
691
|
+
var errJson = (e) => json(e.toJSON(), ACCESS_ERROR_STATUS[e.code]);
|
|
692
|
+
function createBwalletXAccess(o) {
|
|
693
|
+
if (!o.store) throw new Error("createBwalletXAccess needs a nonce store");
|
|
694
|
+
const require2 = normaliseRequirement(o.require);
|
|
695
|
+
const reqHash = canonicalJson(require2);
|
|
696
|
+
const cookieName = o.cookieName ?? "bwx_access";
|
|
697
|
+
const maxAge = o.maxAge ?? 24 * 3600;
|
|
698
|
+
const recheckMs = (o.recheckEvery ?? 300) * 1e3;
|
|
699
|
+
const secure = o.secureCookie ?? o.origin.startsWith("https:");
|
|
700
|
+
const secret = () => {
|
|
701
|
+
const s = o.secret ?? (typeof process !== "undefined" ? process.env?.[o.secretEnv ?? "BWALLETX_SESSION_SECRET"] : void 0);
|
|
702
|
+
assertSecret(s);
|
|
703
|
+
return s;
|
|
704
|
+
};
|
|
705
|
+
const cookie = (v, age) => serializeCookie(v, { name: cookieName, maxAge: age, secure, sameSite: o.sameSite, domain: o.cookieDomain });
|
|
706
|
+
const badOrigin = (req) => !originAllowed(req.headers.get("origin"), o.origin);
|
|
707
|
+
const checked = /* @__PURE__ */ new Map();
|
|
708
|
+
const readToken = async (source) => {
|
|
709
|
+
if (typeof source === "string") return readCookie(source, cookieName);
|
|
710
|
+
if (source instanceof Headers) return readCookie(source.get("cookie"), cookieName);
|
|
711
|
+
if (source && typeof source.headers?.get === "function") return readCookie(source.headers.get("cookie"), cookieName);
|
|
712
|
+
const { cookies } = await import("next/headers");
|
|
713
|
+
const jar = await cookies();
|
|
714
|
+
return jar.get(cookieName)?.value ?? null;
|
|
715
|
+
};
|
|
716
|
+
const gate = {
|
|
717
|
+
require: require2,
|
|
718
|
+
async challenge(req) {
|
|
719
|
+
secret();
|
|
720
|
+
if (badOrigin(req)) return json({ error: "bad_request", message: "origin not allowed" }, 403);
|
|
721
|
+
const b = await body(req);
|
|
722
|
+
try {
|
|
723
|
+
return json(await createAccessChallenge({ origin: o.origin, require: require2, store: o.store, identityKey: b?.identityKey }));
|
|
724
|
+
} catch (e) {
|
|
725
|
+
return errJson(e instanceof AccessError ? e : new AccessError("bad_request", e.message));
|
|
726
|
+
}
|
|
727
|
+
},
|
|
728
|
+
async verify(req) {
|
|
729
|
+
const key = secret();
|
|
730
|
+
if (badOrigin(req)) return json({ error: "bad_request", message: "origin not allowed" }, 403);
|
|
731
|
+
const b = await body(req);
|
|
732
|
+
try {
|
|
733
|
+
const g = await verifyAccess({ origin: o.origin, response: b, store: o.store, resolver: o.resolver, require: require2 });
|
|
734
|
+
const token = await signSession({ sub: g.identityKey, acc: reqHash, ad: g.addresses }, { secret: key, maxAge });
|
|
735
|
+
checked.set(`${g.identityKey}|${g.addresses.join(",")}`, Date.now());
|
|
736
|
+
return json({ ok: true, identityKey: g.identityKey, have: g.have }, 200, { "set-cookie": cookie(token, maxAge) });
|
|
737
|
+
} catch (e) {
|
|
738
|
+
if (e instanceof AccessError) return errJson(e);
|
|
739
|
+
throw e;
|
|
740
|
+
}
|
|
741
|
+
},
|
|
742
|
+
async logout(req) {
|
|
743
|
+
if (badOrigin(req)) return json({ error: "bad_request", message: "origin not allowed" }, 403);
|
|
744
|
+
return json({ ok: true }, 200, { "set-cookie": cookie("", 0) });
|
|
745
|
+
},
|
|
746
|
+
async status(req) {
|
|
747
|
+
const a = await gate.getAccess(req);
|
|
748
|
+
return json(a, 200);
|
|
749
|
+
},
|
|
750
|
+
async getAccess(source) {
|
|
751
|
+
const s = await verifySession(await readToken(source), { secret: secret() });
|
|
752
|
+
if (!s || s.acc !== reqHash) return { ok: false, error: "no_session", message: "Not signed in to this gate." };
|
|
753
|
+
const addresses = Array.isArray(s.ad) ? s.ad.filter((x) => typeof x === "string") : [];
|
|
754
|
+
const k = `${s.sub}|${addresses.join(",")}`;
|
|
755
|
+
const last = checked.get(k) ?? 0;
|
|
756
|
+
if (require2.token && Date.now() - last >= recheckMs) {
|
|
757
|
+
try {
|
|
758
|
+
await checkTokenHolding(require2, addresses, o.resolver);
|
|
759
|
+
checked.set(k, Date.now());
|
|
760
|
+
} catch (e) {
|
|
761
|
+
checked.delete(k);
|
|
762
|
+
const ae = e instanceof AccessError ? e : new AccessError("indexer_unavailable", e.message);
|
|
763
|
+
return { ok: false, error: ae.code, message: ae.message === ae.code ? accessErrorMessage(ae.code, ae) : ae.message, have: ae.have, need: ae.need };
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
return { ok: true, identityKey: s.sub, addresses, checkedAt: checked.get(k) ?? last };
|
|
767
|
+
}
|
|
768
|
+
};
|
|
769
|
+
return gate;
|
|
770
|
+
}
|
|
404
771
|
// Annotate the CommonJS export names for ESM import in node:
|
|
405
772
|
0 && (module.exports = {
|
|
406
773
|
IdentityLinkConflictError,
|
|
774
|
+
createBwalletXAccess,
|
|
407
775
|
createBwalletXAuth,
|
|
408
776
|
linkIdentity,
|
|
409
777
|
memoryStore,
|