@civiwave/vineyard-id 0.1.1
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/dist/index.cjs +277 -0
- package/dist/index.d.cts +208 -0
- package/dist/index.d.ts +208 -0
- package/dist/index.js +231 -0
- package/package.json +35 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
authenticate: () => authenticate,
|
|
34
|
+
constructMessage: () => constructMessage,
|
|
35
|
+
extractNetworkId: () => extractNetworkId,
|
|
36
|
+
generateChallenge: () => generateChallenge,
|
|
37
|
+
issueToken: () => issueToken,
|
|
38
|
+
requestChallenge: () => requestChallenge,
|
|
39
|
+
signChallenge: () => signChallenge,
|
|
40
|
+
validateToken: () => validateToken,
|
|
41
|
+
validateTokenLite: () => validateTokenLite,
|
|
42
|
+
verifyDIDSignature: () => verifyDIDSignature
|
|
43
|
+
});
|
|
44
|
+
module.exports = __toCommonJS(index_exports);
|
|
45
|
+
|
|
46
|
+
// src/challenge.ts
|
|
47
|
+
var import_crypto = __toESM(require("crypto"), 1);
|
|
48
|
+
var DEFAULT_TTL_SECONDS = 300;
|
|
49
|
+
function generateChallenge(did, ttlSeconds = DEFAULT_TTL_SECONDS) {
|
|
50
|
+
const nonce = import_crypto.default.randomBytes(32).toString("hex");
|
|
51
|
+
const expiresAt = Date.now() + ttlSeconds * 1e3;
|
|
52
|
+
return { nonce, expiresAt, did };
|
|
53
|
+
}
|
|
54
|
+
function extractNetworkId(did) {
|
|
55
|
+
const parts = did.split(":");
|
|
56
|
+
if (parts.length < 4 || parts[0] !== "did" || parts[1] !== "vineyard") {
|
|
57
|
+
throw new Error(`Invalid did:vineyard format: ${did}`);
|
|
58
|
+
}
|
|
59
|
+
return parts[2];
|
|
60
|
+
}
|
|
61
|
+
function constructMessage(nonce, timestamp, origin, network = "0") {
|
|
62
|
+
return JSON.stringify({
|
|
63
|
+
domain: "civiwave:did:auth-challenge",
|
|
64
|
+
network,
|
|
65
|
+
nonce,
|
|
66
|
+
timestamp,
|
|
67
|
+
origin
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// src/verify.ts
|
|
72
|
+
var import_util_crypto = require("@polkadot/util-crypto");
|
|
73
|
+
var import_util = require("@polkadot/util");
|
|
74
|
+
var DEFAULT_RESOLVER_ENDPOINT = "http://localhost:8080";
|
|
75
|
+
function decodePublicKeyMultibase(multibase) {
|
|
76
|
+
if (!multibase.startsWith("z")) {
|
|
77
|
+
throw new Error("Only base58btc (z-prefix) multibase is supported");
|
|
78
|
+
}
|
|
79
|
+
const encoded = multibase.slice(1);
|
|
80
|
+
const alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
|
81
|
+
let num = BigInt(0);
|
|
82
|
+
for (const char of encoded) {
|
|
83
|
+
const idx = alphabet.indexOf(char);
|
|
84
|
+
if (idx === -1) throw new Error(`Invalid base58 character: ${char}`);
|
|
85
|
+
num = num * BigInt(58) + BigInt(idx);
|
|
86
|
+
}
|
|
87
|
+
const hex = num.toString(16).padStart(2, "0");
|
|
88
|
+
const bytes = new Uint8Array(
|
|
89
|
+
(hex.length % 2 === 0 ? hex : "0" + hex).match(/.{2}/g).map((b) => parseInt(b, 16))
|
|
90
|
+
);
|
|
91
|
+
const rawKey = bytes.slice(2);
|
|
92
|
+
return (0, import_util.u8aToHex)(rawKey);
|
|
93
|
+
}
|
|
94
|
+
async function resolveViaHttp(did, resolverEndpoint) {
|
|
95
|
+
const url = `${resolverEndpoint}/1.0/identifiers/${encodeURIComponent(did)}`;
|
|
96
|
+
const response = await fetch(url, {
|
|
97
|
+
headers: { Accept: "application/did+ld+json" }
|
|
98
|
+
});
|
|
99
|
+
if (!response.ok) {
|
|
100
|
+
return {
|
|
101
|
+
didDocument: null,
|
|
102
|
+
didResolutionMetadata: { error: "notFound" }
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
return await response.json();
|
|
106
|
+
}
|
|
107
|
+
function extractPublicKey(didDocument) {
|
|
108
|
+
if (!didDocument?.verificationMethod?.length) {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
for (const vm of didDocument.verificationMethod) {
|
|
112
|
+
if (vm.publicKeyMultibase) {
|
|
113
|
+
const publicKeyHex = decodePublicKeyMultibase(vm.publicKeyMultibase);
|
|
114
|
+
return { publicKeyHex, keyType: vm.type };
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
async function verifyDIDSignature(request, resolverEndpoint = DEFAULT_RESOLVER_ENDPOINT) {
|
|
120
|
+
try {
|
|
121
|
+
const resolution = await resolveViaHttp(request.did, resolverEndpoint);
|
|
122
|
+
if (resolution.didResolutionMetadata.error || !resolution.didDocument) {
|
|
123
|
+
return { valid: false };
|
|
124
|
+
}
|
|
125
|
+
const keyInfo = extractPublicKey(resolution.didDocument);
|
|
126
|
+
if (!keyInfo) {
|
|
127
|
+
return { valid: false };
|
|
128
|
+
}
|
|
129
|
+
const network = extractNetworkId(request.did);
|
|
130
|
+
const message = constructMessage(
|
|
131
|
+
request.nonce,
|
|
132
|
+
request.timestamp,
|
|
133
|
+
request.origin,
|
|
134
|
+
network
|
|
135
|
+
);
|
|
136
|
+
const messageU8a = (0, import_util.stringToU8a)(message);
|
|
137
|
+
const result = (0, import_util_crypto.signatureVerify)(
|
|
138
|
+
messageU8a,
|
|
139
|
+
request.signature,
|
|
140
|
+
keyInfo.publicKeyHex
|
|
141
|
+
);
|
|
142
|
+
return {
|
|
143
|
+
valid: result.isValid,
|
|
144
|
+
publicKey: keyInfo.publicKeyHex,
|
|
145
|
+
keyType: keyInfo.keyType
|
|
146
|
+
};
|
|
147
|
+
} catch (error) {
|
|
148
|
+
console.error("[did-auth] Signature verification error:", error);
|
|
149
|
+
return { valid: false };
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// src/session.ts
|
|
154
|
+
var import_jsonwebtoken = __toESM(require("jsonwebtoken"), 1);
|
|
155
|
+
var DEFAULT_EXPIRES_IN = "7d";
|
|
156
|
+
function issueToken(did, ss58, tier, secret, expiresIn = DEFAULT_EXPIRES_IN) {
|
|
157
|
+
return import_jsonwebtoken.default.sign(
|
|
158
|
+
{
|
|
159
|
+
sub: did,
|
|
160
|
+
ss58,
|
|
161
|
+
tier
|
|
162
|
+
},
|
|
163
|
+
secret,
|
|
164
|
+
{ expiresIn }
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
function validateToken(token, secret) {
|
|
168
|
+
try {
|
|
169
|
+
const payload = import_jsonwebtoken.default.verify(token, secret);
|
|
170
|
+
if (!payload.sub || !payload.ss58) {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
return {
|
|
174
|
+
did: payload.sub,
|
|
175
|
+
ss58: payload.ss58,
|
|
176
|
+
tier: payload.tier || "member",
|
|
177
|
+
iat: payload.iat ?? 0,
|
|
178
|
+
exp: payload.exp ?? 0
|
|
179
|
+
};
|
|
180
|
+
} catch {
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
function validateTokenLite(token) {
|
|
185
|
+
try {
|
|
186
|
+
const parts = token.split(".");
|
|
187
|
+
if (parts.length !== 3) return null;
|
|
188
|
+
const payload = JSON.parse(
|
|
189
|
+
Buffer.from(parts[1], "base64url").toString("utf-8")
|
|
190
|
+
);
|
|
191
|
+
if (!payload.sub || !payload.ss58) return null;
|
|
192
|
+
if (payload.exp && payload.exp * 1e3 < Date.now()) return null;
|
|
193
|
+
return {
|
|
194
|
+
did: payload.sub,
|
|
195
|
+
ss58: payload.ss58,
|
|
196
|
+
tier: payload.tier || "member",
|
|
197
|
+
iat: payload.iat ?? 0,
|
|
198
|
+
exp: payload.exp ?? 0
|
|
199
|
+
};
|
|
200
|
+
} catch {
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// src/client.ts
|
|
206
|
+
async function requestChallenge(serverUrl, did) {
|
|
207
|
+
const response = await fetch(`${serverUrl}/auth/did/challenge`, {
|
|
208
|
+
method: "POST",
|
|
209
|
+
headers: { "Content-Type": "application/json" },
|
|
210
|
+
body: JSON.stringify({ did })
|
|
211
|
+
});
|
|
212
|
+
if (!response.ok) {
|
|
213
|
+
const error = await response.json().catch(() => ({ error: "Challenge request failed" }));
|
|
214
|
+
throw new Error(error.error || "Challenge request failed");
|
|
215
|
+
}
|
|
216
|
+
const data = await response.json();
|
|
217
|
+
return {
|
|
218
|
+
nonce: data.nonce,
|
|
219
|
+
expiresAt: data.expiresAt,
|
|
220
|
+
did
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
async function signChallenge(nonce, timestamp, origin, signer, address, network = "0") {
|
|
224
|
+
if (!signer.signRaw) {
|
|
225
|
+
throw new Error("Signer does not support signRaw");
|
|
226
|
+
}
|
|
227
|
+
const message = constructMessage(nonce, timestamp, origin, network);
|
|
228
|
+
const result = await signer.signRaw({
|
|
229
|
+
address,
|
|
230
|
+
data: message,
|
|
231
|
+
type: "bytes"
|
|
232
|
+
});
|
|
233
|
+
return { signature: result.signature };
|
|
234
|
+
}
|
|
235
|
+
async function authenticate(serverUrl, did, signer, address) {
|
|
236
|
+
const challenge = await requestChallenge(serverUrl, did);
|
|
237
|
+
const timestamp = Date.now();
|
|
238
|
+
const origin = typeof window !== "undefined" ? window.location.origin : "";
|
|
239
|
+
const network = extractNetworkId(did);
|
|
240
|
+
const { signature } = await signChallenge(
|
|
241
|
+
challenge.nonce,
|
|
242
|
+
timestamp,
|
|
243
|
+
origin,
|
|
244
|
+
signer,
|
|
245
|
+
address,
|
|
246
|
+
network
|
|
247
|
+
);
|
|
248
|
+
const response = await fetch(`${serverUrl}/auth/did/verify`, {
|
|
249
|
+
method: "POST",
|
|
250
|
+
headers: { "Content-Type": "application/json" },
|
|
251
|
+
body: JSON.stringify({
|
|
252
|
+
did,
|
|
253
|
+
nonce: challenge.nonce,
|
|
254
|
+
signature,
|
|
255
|
+
timestamp,
|
|
256
|
+
origin
|
|
257
|
+
})
|
|
258
|
+
});
|
|
259
|
+
if (!response.ok) {
|
|
260
|
+
const error = await response.json().catch(() => ({ error: "Verification failed" }));
|
|
261
|
+
throw new Error(error.error || "DID authentication failed");
|
|
262
|
+
}
|
|
263
|
+
return await response.json();
|
|
264
|
+
}
|
|
265
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
266
|
+
0 && (module.exports = {
|
|
267
|
+
authenticate,
|
|
268
|
+
constructMessage,
|
|
269
|
+
extractNetworkId,
|
|
270
|
+
generateChallenge,
|
|
271
|
+
issueToken,
|
|
272
|
+
requestChallenge,
|
|
273
|
+
signChallenge,
|
|
274
|
+
validateToken,
|
|
275
|
+
validateTokenLite,
|
|
276
|
+
verifyDIDSignature
|
|
277
|
+
});
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DID Authentication types for the sign-in-with-DID protocol.
|
|
3
|
+
*/
|
|
4
|
+
/** A challenge issued to a DID holder for authentication. */
|
|
5
|
+
interface DIDChallenge {
|
|
6
|
+
nonce: string;
|
|
7
|
+
expiresAt: number;
|
|
8
|
+
did: string;
|
|
9
|
+
}
|
|
10
|
+
/** A signed authentication request from the client. */
|
|
11
|
+
interface DIDAuthRequest {
|
|
12
|
+
did: string;
|
|
13
|
+
nonce: string;
|
|
14
|
+
signature: string;
|
|
15
|
+
timestamp: number;
|
|
16
|
+
origin: string;
|
|
17
|
+
}
|
|
18
|
+
/** A validated DID session stored in a JWT. */
|
|
19
|
+
interface DIDSession {
|
|
20
|
+
did: string;
|
|
21
|
+
ss58: string;
|
|
22
|
+
tier: string;
|
|
23
|
+
iat: number;
|
|
24
|
+
exp: number;
|
|
25
|
+
}
|
|
26
|
+
/** Result of DID signature verification. */
|
|
27
|
+
interface DIDVerifyResult {
|
|
28
|
+
valid: boolean;
|
|
29
|
+
publicKey?: string;
|
|
30
|
+
keyType?: string;
|
|
31
|
+
}
|
|
32
|
+
/** Minimal DID Document shape needed for verification. */
|
|
33
|
+
interface DIDDocumentLike {
|
|
34
|
+
id: string;
|
|
35
|
+
verificationMethod?: Array<{
|
|
36
|
+
id: string;
|
|
37
|
+
type: string;
|
|
38
|
+
controller: string;
|
|
39
|
+
publicKeyMultibase?: string;
|
|
40
|
+
}>;
|
|
41
|
+
}
|
|
42
|
+
/** DID resolution result shape (subset of DIF Universal Resolver). */
|
|
43
|
+
interface DIDResolutionResultLike {
|
|
44
|
+
didDocument: DIDDocumentLike | null;
|
|
45
|
+
didResolutionMetadata: {
|
|
46
|
+
error?: string;
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Challenge generation and message construction for DID auth.
|
|
52
|
+
*/
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Generate a cryptographic challenge for DID authentication.
|
|
56
|
+
*
|
|
57
|
+
* @param did - The DID requesting authentication
|
|
58
|
+
* @param ttlSeconds - Challenge time-to-live in seconds (default: 300)
|
|
59
|
+
* @returns A DIDChallenge with a random nonce and expiration timestamp
|
|
60
|
+
*/
|
|
61
|
+
declare function generateChallenge(did: string, ttlSeconds?: number): DIDChallenge;
|
|
62
|
+
/**
|
|
63
|
+
* Extract the network ID from a did:vineyard DID.
|
|
64
|
+
* Format: did:vineyard:<networkId>:<ss58Address>
|
|
65
|
+
*
|
|
66
|
+
* @param did - A did:vineyard DID string
|
|
67
|
+
* @returns The network ID string (e.g., "0" for devnet, "1" for mainnet)
|
|
68
|
+
*/
|
|
69
|
+
declare function extractNetworkId(did: string): string;
|
|
70
|
+
/**
|
|
71
|
+
* Construct the canonical message to be signed by the wallet.
|
|
72
|
+
*
|
|
73
|
+
* The message is a JSON string with domain separation to prevent
|
|
74
|
+
* signature replay across different protocols and chains. The network
|
|
75
|
+
* field binds the signature to a specific chain (devnet/testnet/mainnet),
|
|
76
|
+
* preventing cross-chain replay attacks.
|
|
77
|
+
*
|
|
78
|
+
* @param nonce - The challenge nonce
|
|
79
|
+
* @param timestamp - Client-provided timestamp
|
|
80
|
+
* @param origin - The requesting origin (e.g., "https://axis.civiwave.network")
|
|
81
|
+
* @param network - Chain network ID from the DID (e.g., "0" for devnet)
|
|
82
|
+
* @returns The canonical message string to be signed
|
|
83
|
+
*/
|
|
84
|
+
declare function constructMessage(nonce: string, timestamp: number, origin: string, network?: string): string;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* DID signature verification for authentication.
|
|
88
|
+
*
|
|
89
|
+
* Resolves the DID to obtain the public key, then verifies the signature
|
|
90
|
+
* against the constructed message using @polkadot/util-crypto.
|
|
91
|
+
*/
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Verify a DID authentication signature.
|
|
95
|
+
*
|
|
96
|
+
* 1. Resolves the DID to obtain the public key
|
|
97
|
+
* 2. Constructs the canonical message from the request parameters
|
|
98
|
+
* 3. Verifies the signature using @polkadot/util-crypto signatureVerify
|
|
99
|
+
*
|
|
100
|
+
* @param request - The authentication request containing DID, nonce, signature, etc.
|
|
101
|
+
* @param resolverEndpoint - Optional HTTP resolver endpoint URL
|
|
102
|
+
* @returns Verification result with validity flag and key info
|
|
103
|
+
*/
|
|
104
|
+
declare function verifyDIDSignature(request: DIDAuthRequest, resolverEndpoint?: string): Promise<DIDVerifyResult>;
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* JWT session management for DID authentication.
|
|
108
|
+
*
|
|
109
|
+
* Issues and validates JWTs containing DID session information.
|
|
110
|
+
*/
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Issue a JWT token for an authenticated DID session.
|
|
114
|
+
*
|
|
115
|
+
* @param did - The authenticated DID (e.g., "did:vineyard:0:<ss58>")
|
|
116
|
+
* @param ss58 - The SS58 address extracted from the DID
|
|
117
|
+
* @param tier - The user's tier/role (e.g., "member", "validator")
|
|
118
|
+
* @param secret - JWT signing secret (from DID_AUTH_SECRET env var)
|
|
119
|
+
* @param expiresIn - JWT expiration (default: "7d")
|
|
120
|
+
* @returns Signed JWT string
|
|
121
|
+
*/
|
|
122
|
+
declare function issueToken(did: string, ss58: string, tier: string, secret: string, expiresIn?: string): string;
|
|
123
|
+
/**
|
|
124
|
+
* Validate a DID JWT token and extract the session payload.
|
|
125
|
+
*
|
|
126
|
+
* @param token - The JWT string to validate
|
|
127
|
+
* @param secret - JWT signing secret (must match issuing secret)
|
|
128
|
+
* @returns The DIDSession if valid, or null if invalid/expired
|
|
129
|
+
*/
|
|
130
|
+
declare function validateToken(token: string, secret: string): DIDSession | null;
|
|
131
|
+
/**
|
|
132
|
+
* Lightweight JWT validation without full jsonwebtoken dependency.
|
|
133
|
+
* Suitable for Edge runtime (Next.js middleware) where jsonwebtoken isn't available.
|
|
134
|
+
*
|
|
135
|
+
* WARNING: This only validates structure and expiration, NOT the signature.
|
|
136
|
+
* Use validateToken() for full server-side validation.
|
|
137
|
+
*
|
|
138
|
+
* @param token - The JWT string
|
|
139
|
+
* @returns Parsed session or null
|
|
140
|
+
*/
|
|
141
|
+
declare function validateTokenLite(token: string): DIDSession | null;
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Client-side DID authentication helpers.
|
|
145
|
+
*
|
|
146
|
+
* Used in browser contexts to perform the challenge-response flow
|
|
147
|
+
* with a Polkadot wallet extension signer.
|
|
148
|
+
*/
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Request a challenge from the server for DID authentication.
|
|
152
|
+
*
|
|
153
|
+
* @param serverUrl - The base server URL (e.g., "/api" or "http://localhost:3001/api")
|
|
154
|
+
* @param did - The DID to authenticate
|
|
155
|
+
* @returns The challenge containing nonce and expiration
|
|
156
|
+
*/
|
|
157
|
+
declare function requestChallenge(serverUrl: string, did: string): Promise<DIDChallenge>;
|
|
158
|
+
/**
|
|
159
|
+
* Sign a challenge using a Polkadot wallet extension signer.
|
|
160
|
+
*
|
|
161
|
+
* @param nonce - The challenge nonce from the server
|
|
162
|
+
* @param timestamp - Current timestamp
|
|
163
|
+
* @param origin - The current page origin
|
|
164
|
+
* @param signer - The signer from @polkadot/extension-dapp web3FromSource()
|
|
165
|
+
* @param address - The SS58 address to sign with
|
|
166
|
+
* @returns The hex-encoded signature
|
|
167
|
+
*/
|
|
168
|
+
declare function signChallenge(nonce: string, timestamp: number, origin: string, signer: {
|
|
169
|
+
signRaw?: (payload: {
|
|
170
|
+
address: string;
|
|
171
|
+
data: string;
|
|
172
|
+
type: string;
|
|
173
|
+
}) => Promise<{
|
|
174
|
+
signature: string;
|
|
175
|
+
}>;
|
|
176
|
+
}, address: string, network?: string): Promise<{
|
|
177
|
+
signature: string;
|
|
178
|
+
}>;
|
|
179
|
+
/**
|
|
180
|
+
* Perform the full DID authentication flow.
|
|
181
|
+
*
|
|
182
|
+
* 1. Request challenge from server
|
|
183
|
+
* 2. Sign challenge with wallet
|
|
184
|
+
* 3. Submit signed challenge for verification
|
|
185
|
+
* 4. Return the JWT token on success
|
|
186
|
+
*
|
|
187
|
+
* @param serverUrl - Base server URL for API calls
|
|
188
|
+
* @param did - The DID to authenticate
|
|
189
|
+
* @param signer - Polkadot wallet signer
|
|
190
|
+
* @param address - SS58 address for signing
|
|
191
|
+
* @returns Authentication result with token and session info
|
|
192
|
+
*/
|
|
193
|
+
declare function authenticate(serverUrl: string, did: string, signer: {
|
|
194
|
+
signRaw?: (payload: {
|
|
195
|
+
address: string;
|
|
196
|
+
data: string;
|
|
197
|
+
type: string;
|
|
198
|
+
}) => Promise<{
|
|
199
|
+
signature: string;
|
|
200
|
+
}>;
|
|
201
|
+
}, address: string): Promise<{
|
|
202
|
+
token: string;
|
|
203
|
+
did: string;
|
|
204
|
+
ss58: string;
|
|
205
|
+
tier: string;
|
|
206
|
+
}>;
|
|
207
|
+
|
|
208
|
+
export { type DIDAuthRequest, type DIDChallenge, type DIDDocumentLike, type DIDResolutionResultLike, type DIDSession, type DIDVerifyResult, authenticate, constructMessage, extractNetworkId, generateChallenge, issueToken, requestChallenge, signChallenge, validateToken, validateTokenLite, verifyDIDSignature };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DID Authentication types for the sign-in-with-DID protocol.
|
|
3
|
+
*/
|
|
4
|
+
/** A challenge issued to a DID holder for authentication. */
|
|
5
|
+
interface DIDChallenge {
|
|
6
|
+
nonce: string;
|
|
7
|
+
expiresAt: number;
|
|
8
|
+
did: string;
|
|
9
|
+
}
|
|
10
|
+
/** A signed authentication request from the client. */
|
|
11
|
+
interface DIDAuthRequest {
|
|
12
|
+
did: string;
|
|
13
|
+
nonce: string;
|
|
14
|
+
signature: string;
|
|
15
|
+
timestamp: number;
|
|
16
|
+
origin: string;
|
|
17
|
+
}
|
|
18
|
+
/** A validated DID session stored in a JWT. */
|
|
19
|
+
interface DIDSession {
|
|
20
|
+
did: string;
|
|
21
|
+
ss58: string;
|
|
22
|
+
tier: string;
|
|
23
|
+
iat: number;
|
|
24
|
+
exp: number;
|
|
25
|
+
}
|
|
26
|
+
/** Result of DID signature verification. */
|
|
27
|
+
interface DIDVerifyResult {
|
|
28
|
+
valid: boolean;
|
|
29
|
+
publicKey?: string;
|
|
30
|
+
keyType?: string;
|
|
31
|
+
}
|
|
32
|
+
/** Minimal DID Document shape needed for verification. */
|
|
33
|
+
interface DIDDocumentLike {
|
|
34
|
+
id: string;
|
|
35
|
+
verificationMethod?: Array<{
|
|
36
|
+
id: string;
|
|
37
|
+
type: string;
|
|
38
|
+
controller: string;
|
|
39
|
+
publicKeyMultibase?: string;
|
|
40
|
+
}>;
|
|
41
|
+
}
|
|
42
|
+
/** DID resolution result shape (subset of DIF Universal Resolver). */
|
|
43
|
+
interface DIDResolutionResultLike {
|
|
44
|
+
didDocument: DIDDocumentLike | null;
|
|
45
|
+
didResolutionMetadata: {
|
|
46
|
+
error?: string;
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Challenge generation and message construction for DID auth.
|
|
52
|
+
*/
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Generate a cryptographic challenge for DID authentication.
|
|
56
|
+
*
|
|
57
|
+
* @param did - The DID requesting authentication
|
|
58
|
+
* @param ttlSeconds - Challenge time-to-live in seconds (default: 300)
|
|
59
|
+
* @returns A DIDChallenge with a random nonce and expiration timestamp
|
|
60
|
+
*/
|
|
61
|
+
declare function generateChallenge(did: string, ttlSeconds?: number): DIDChallenge;
|
|
62
|
+
/**
|
|
63
|
+
* Extract the network ID from a did:vineyard DID.
|
|
64
|
+
* Format: did:vineyard:<networkId>:<ss58Address>
|
|
65
|
+
*
|
|
66
|
+
* @param did - A did:vineyard DID string
|
|
67
|
+
* @returns The network ID string (e.g., "0" for devnet, "1" for mainnet)
|
|
68
|
+
*/
|
|
69
|
+
declare function extractNetworkId(did: string): string;
|
|
70
|
+
/**
|
|
71
|
+
* Construct the canonical message to be signed by the wallet.
|
|
72
|
+
*
|
|
73
|
+
* The message is a JSON string with domain separation to prevent
|
|
74
|
+
* signature replay across different protocols and chains. The network
|
|
75
|
+
* field binds the signature to a specific chain (devnet/testnet/mainnet),
|
|
76
|
+
* preventing cross-chain replay attacks.
|
|
77
|
+
*
|
|
78
|
+
* @param nonce - The challenge nonce
|
|
79
|
+
* @param timestamp - Client-provided timestamp
|
|
80
|
+
* @param origin - The requesting origin (e.g., "https://axis.civiwave.network")
|
|
81
|
+
* @param network - Chain network ID from the DID (e.g., "0" for devnet)
|
|
82
|
+
* @returns The canonical message string to be signed
|
|
83
|
+
*/
|
|
84
|
+
declare function constructMessage(nonce: string, timestamp: number, origin: string, network?: string): string;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* DID signature verification for authentication.
|
|
88
|
+
*
|
|
89
|
+
* Resolves the DID to obtain the public key, then verifies the signature
|
|
90
|
+
* against the constructed message using @polkadot/util-crypto.
|
|
91
|
+
*/
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Verify a DID authentication signature.
|
|
95
|
+
*
|
|
96
|
+
* 1. Resolves the DID to obtain the public key
|
|
97
|
+
* 2. Constructs the canonical message from the request parameters
|
|
98
|
+
* 3. Verifies the signature using @polkadot/util-crypto signatureVerify
|
|
99
|
+
*
|
|
100
|
+
* @param request - The authentication request containing DID, nonce, signature, etc.
|
|
101
|
+
* @param resolverEndpoint - Optional HTTP resolver endpoint URL
|
|
102
|
+
* @returns Verification result with validity flag and key info
|
|
103
|
+
*/
|
|
104
|
+
declare function verifyDIDSignature(request: DIDAuthRequest, resolverEndpoint?: string): Promise<DIDVerifyResult>;
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* JWT session management for DID authentication.
|
|
108
|
+
*
|
|
109
|
+
* Issues and validates JWTs containing DID session information.
|
|
110
|
+
*/
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Issue a JWT token for an authenticated DID session.
|
|
114
|
+
*
|
|
115
|
+
* @param did - The authenticated DID (e.g., "did:vineyard:0:<ss58>")
|
|
116
|
+
* @param ss58 - The SS58 address extracted from the DID
|
|
117
|
+
* @param tier - The user's tier/role (e.g., "member", "validator")
|
|
118
|
+
* @param secret - JWT signing secret (from DID_AUTH_SECRET env var)
|
|
119
|
+
* @param expiresIn - JWT expiration (default: "7d")
|
|
120
|
+
* @returns Signed JWT string
|
|
121
|
+
*/
|
|
122
|
+
declare function issueToken(did: string, ss58: string, tier: string, secret: string, expiresIn?: string): string;
|
|
123
|
+
/**
|
|
124
|
+
* Validate a DID JWT token and extract the session payload.
|
|
125
|
+
*
|
|
126
|
+
* @param token - The JWT string to validate
|
|
127
|
+
* @param secret - JWT signing secret (must match issuing secret)
|
|
128
|
+
* @returns The DIDSession if valid, or null if invalid/expired
|
|
129
|
+
*/
|
|
130
|
+
declare function validateToken(token: string, secret: string): DIDSession | null;
|
|
131
|
+
/**
|
|
132
|
+
* Lightweight JWT validation without full jsonwebtoken dependency.
|
|
133
|
+
* Suitable for Edge runtime (Next.js middleware) where jsonwebtoken isn't available.
|
|
134
|
+
*
|
|
135
|
+
* WARNING: This only validates structure and expiration, NOT the signature.
|
|
136
|
+
* Use validateToken() for full server-side validation.
|
|
137
|
+
*
|
|
138
|
+
* @param token - The JWT string
|
|
139
|
+
* @returns Parsed session or null
|
|
140
|
+
*/
|
|
141
|
+
declare function validateTokenLite(token: string): DIDSession | null;
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Client-side DID authentication helpers.
|
|
145
|
+
*
|
|
146
|
+
* Used in browser contexts to perform the challenge-response flow
|
|
147
|
+
* with a Polkadot wallet extension signer.
|
|
148
|
+
*/
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Request a challenge from the server for DID authentication.
|
|
152
|
+
*
|
|
153
|
+
* @param serverUrl - The base server URL (e.g., "/api" or "http://localhost:3001/api")
|
|
154
|
+
* @param did - The DID to authenticate
|
|
155
|
+
* @returns The challenge containing nonce and expiration
|
|
156
|
+
*/
|
|
157
|
+
declare function requestChallenge(serverUrl: string, did: string): Promise<DIDChallenge>;
|
|
158
|
+
/**
|
|
159
|
+
* Sign a challenge using a Polkadot wallet extension signer.
|
|
160
|
+
*
|
|
161
|
+
* @param nonce - The challenge nonce from the server
|
|
162
|
+
* @param timestamp - Current timestamp
|
|
163
|
+
* @param origin - The current page origin
|
|
164
|
+
* @param signer - The signer from @polkadot/extension-dapp web3FromSource()
|
|
165
|
+
* @param address - The SS58 address to sign with
|
|
166
|
+
* @returns The hex-encoded signature
|
|
167
|
+
*/
|
|
168
|
+
declare function signChallenge(nonce: string, timestamp: number, origin: string, signer: {
|
|
169
|
+
signRaw?: (payload: {
|
|
170
|
+
address: string;
|
|
171
|
+
data: string;
|
|
172
|
+
type: string;
|
|
173
|
+
}) => Promise<{
|
|
174
|
+
signature: string;
|
|
175
|
+
}>;
|
|
176
|
+
}, address: string, network?: string): Promise<{
|
|
177
|
+
signature: string;
|
|
178
|
+
}>;
|
|
179
|
+
/**
|
|
180
|
+
* Perform the full DID authentication flow.
|
|
181
|
+
*
|
|
182
|
+
* 1. Request challenge from server
|
|
183
|
+
* 2. Sign challenge with wallet
|
|
184
|
+
* 3. Submit signed challenge for verification
|
|
185
|
+
* 4. Return the JWT token on success
|
|
186
|
+
*
|
|
187
|
+
* @param serverUrl - Base server URL for API calls
|
|
188
|
+
* @param did - The DID to authenticate
|
|
189
|
+
* @param signer - Polkadot wallet signer
|
|
190
|
+
* @param address - SS58 address for signing
|
|
191
|
+
* @returns Authentication result with token and session info
|
|
192
|
+
*/
|
|
193
|
+
declare function authenticate(serverUrl: string, did: string, signer: {
|
|
194
|
+
signRaw?: (payload: {
|
|
195
|
+
address: string;
|
|
196
|
+
data: string;
|
|
197
|
+
type: string;
|
|
198
|
+
}) => Promise<{
|
|
199
|
+
signature: string;
|
|
200
|
+
}>;
|
|
201
|
+
}, address: string): Promise<{
|
|
202
|
+
token: string;
|
|
203
|
+
did: string;
|
|
204
|
+
ss58: string;
|
|
205
|
+
tier: string;
|
|
206
|
+
}>;
|
|
207
|
+
|
|
208
|
+
export { type DIDAuthRequest, type DIDChallenge, type DIDDocumentLike, type DIDResolutionResultLike, type DIDSession, type DIDVerifyResult, authenticate, constructMessage, extractNetworkId, generateChallenge, issueToken, requestChallenge, signChallenge, validateToken, validateTokenLite, verifyDIDSignature };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
// src/challenge.ts
|
|
2
|
+
import crypto from "crypto";
|
|
3
|
+
var DEFAULT_TTL_SECONDS = 300;
|
|
4
|
+
function generateChallenge(did, ttlSeconds = DEFAULT_TTL_SECONDS) {
|
|
5
|
+
const nonce = crypto.randomBytes(32).toString("hex");
|
|
6
|
+
const expiresAt = Date.now() + ttlSeconds * 1e3;
|
|
7
|
+
return { nonce, expiresAt, did };
|
|
8
|
+
}
|
|
9
|
+
function extractNetworkId(did) {
|
|
10
|
+
const parts = did.split(":");
|
|
11
|
+
if (parts.length < 4 || parts[0] !== "did" || parts[1] !== "vineyard") {
|
|
12
|
+
throw new Error(`Invalid did:vineyard format: ${did}`);
|
|
13
|
+
}
|
|
14
|
+
return parts[2];
|
|
15
|
+
}
|
|
16
|
+
function constructMessage(nonce, timestamp, origin, network = "0") {
|
|
17
|
+
return JSON.stringify({
|
|
18
|
+
domain: "civiwave:did:auth-challenge",
|
|
19
|
+
network,
|
|
20
|
+
nonce,
|
|
21
|
+
timestamp,
|
|
22
|
+
origin
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// src/verify.ts
|
|
27
|
+
import { signatureVerify } from "@polkadot/util-crypto";
|
|
28
|
+
import { u8aToHex, stringToU8a } from "@polkadot/util";
|
|
29
|
+
var DEFAULT_RESOLVER_ENDPOINT = "http://localhost:8080";
|
|
30
|
+
function decodePublicKeyMultibase(multibase) {
|
|
31
|
+
if (!multibase.startsWith("z")) {
|
|
32
|
+
throw new Error("Only base58btc (z-prefix) multibase is supported");
|
|
33
|
+
}
|
|
34
|
+
const encoded = multibase.slice(1);
|
|
35
|
+
const alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
|
36
|
+
let num = BigInt(0);
|
|
37
|
+
for (const char of encoded) {
|
|
38
|
+
const idx = alphabet.indexOf(char);
|
|
39
|
+
if (idx === -1) throw new Error(`Invalid base58 character: ${char}`);
|
|
40
|
+
num = num * BigInt(58) + BigInt(idx);
|
|
41
|
+
}
|
|
42
|
+
const hex = num.toString(16).padStart(2, "0");
|
|
43
|
+
const bytes = new Uint8Array(
|
|
44
|
+
(hex.length % 2 === 0 ? hex : "0" + hex).match(/.{2}/g).map((b) => parseInt(b, 16))
|
|
45
|
+
);
|
|
46
|
+
const rawKey = bytes.slice(2);
|
|
47
|
+
return u8aToHex(rawKey);
|
|
48
|
+
}
|
|
49
|
+
async function resolveViaHttp(did, resolverEndpoint) {
|
|
50
|
+
const url = `${resolverEndpoint}/1.0/identifiers/${encodeURIComponent(did)}`;
|
|
51
|
+
const response = await fetch(url, {
|
|
52
|
+
headers: { Accept: "application/did+ld+json" }
|
|
53
|
+
});
|
|
54
|
+
if (!response.ok) {
|
|
55
|
+
return {
|
|
56
|
+
didDocument: null,
|
|
57
|
+
didResolutionMetadata: { error: "notFound" }
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
return await response.json();
|
|
61
|
+
}
|
|
62
|
+
function extractPublicKey(didDocument) {
|
|
63
|
+
if (!didDocument?.verificationMethod?.length) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
for (const vm of didDocument.verificationMethod) {
|
|
67
|
+
if (vm.publicKeyMultibase) {
|
|
68
|
+
const publicKeyHex = decodePublicKeyMultibase(vm.publicKeyMultibase);
|
|
69
|
+
return { publicKeyHex, keyType: vm.type };
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
async function verifyDIDSignature(request, resolverEndpoint = DEFAULT_RESOLVER_ENDPOINT) {
|
|
75
|
+
try {
|
|
76
|
+
const resolution = await resolveViaHttp(request.did, resolverEndpoint);
|
|
77
|
+
if (resolution.didResolutionMetadata.error || !resolution.didDocument) {
|
|
78
|
+
return { valid: false };
|
|
79
|
+
}
|
|
80
|
+
const keyInfo = extractPublicKey(resolution.didDocument);
|
|
81
|
+
if (!keyInfo) {
|
|
82
|
+
return { valid: false };
|
|
83
|
+
}
|
|
84
|
+
const network = extractNetworkId(request.did);
|
|
85
|
+
const message = constructMessage(
|
|
86
|
+
request.nonce,
|
|
87
|
+
request.timestamp,
|
|
88
|
+
request.origin,
|
|
89
|
+
network
|
|
90
|
+
);
|
|
91
|
+
const messageU8a = stringToU8a(message);
|
|
92
|
+
const result = signatureVerify(
|
|
93
|
+
messageU8a,
|
|
94
|
+
request.signature,
|
|
95
|
+
keyInfo.publicKeyHex
|
|
96
|
+
);
|
|
97
|
+
return {
|
|
98
|
+
valid: result.isValid,
|
|
99
|
+
publicKey: keyInfo.publicKeyHex,
|
|
100
|
+
keyType: keyInfo.keyType
|
|
101
|
+
};
|
|
102
|
+
} catch (error) {
|
|
103
|
+
console.error("[did-auth] Signature verification error:", error);
|
|
104
|
+
return { valid: false };
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// src/session.ts
|
|
109
|
+
import jwt from "jsonwebtoken";
|
|
110
|
+
var DEFAULT_EXPIRES_IN = "7d";
|
|
111
|
+
function issueToken(did, ss58, tier, secret, expiresIn = DEFAULT_EXPIRES_IN) {
|
|
112
|
+
return jwt.sign(
|
|
113
|
+
{
|
|
114
|
+
sub: did,
|
|
115
|
+
ss58,
|
|
116
|
+
tier
|
|
117
|
+
},
|
|
118
|
+
secret,
|
|
119
|
+
{ expiresIn }
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
function validateToken(token, secret) {
|
|
123
|
+
try {
|
|
124
|
+
const payload = jwt.verify(token, secret);
|
|
125
|
+
if (!payload.sub || !payload.ss58) {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
return {
|
|
129
|
+
did: payload.sub,
|
|
130
|
+
ss58: payload.ss58,
|
|
131
|
+
tier: payload.tier || "member",
|
|
132
|
+
iat: payload.iat ?? 0,
|
|
133
|
+
exp: payload.exp ?? 0
|
|
134
|
+
};
|
|
135
|
+
} catch {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
function validateTokenLite(token) {
|
|
140
|
+
try {
|
|
141
|
+
const parts = token.split(".");
|
|
142
|
+
if (parts.length !== 3) return null;
|
|
143
|
+
const payload = JSON.parse(
|
|
144
|
+
Buffer.from(parts[1], "base64url").toString("utf-8")
|
|
145
|
+
);
|
|
146
|
+
if (!payload.sub || !payload.ss58) return null;
|
|
147
|
+
if (payload.exp && payload.exp * 1e3 < Date.now()) return null;
|
|
148
|
+
return {
|
|
149
|
+
did: payload.sub,
|
|
150
|
+
ss58: payload.ss58,
|
|
151
|
+
tier: payload.tier || "member",
|
|
152
|
+
iat: payload.iat ?? 0,
|
|
153
|
+
exp: payload.exp ?? 0
|
|
154
|
+
};
|
|
155
|
+
} catch {
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// src/client.ts
|
|
161
|
+
async function requestChallenge(serverUrl, did) {
|
|
162
|
+
const response = await fetch(`${serverUrl}/auth/did/challenge`, {
|
|
163
|
+
method: "POST",
|
|
164
|
+
headers: { "Content-Type": "application/json" },
|
|
165
|
+
body: JSON.stringify({ did })
|
|
166
|
+
});
|
|
167
|
+
if (!response.ok) {
|
|
168
|
+
const error = await response.json().catch(() => ({ error: "Challenge request failed" }));
|
|
169
|
+
throw new Error(error.error || "Challenge request failed");
|
|
170
|
+
}
|
|
171
|
+
const data = await response.json();
|
|
172
|
+
return {
|
|
173
|
+
nonce: data.nonce,
|
|
174
|
+
expiresAt: data.expiresAt,
|
|
175
|
+
did
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
async function signChallenge(nonce, timestamp, origin, signer, address, network = "0") {
|
|
179
|
+
if (!signer.signRaw) {
|
|
180
|
+
throw new Error("Signer does not support signRaw");
|
|
181
|
+
}
|
|
182
|
+
const message = constructMessage(nonce, timestamp, origin, network);
|
|
183
|
+
const result = await signer.signRaw({
|
|
184
|
+
address,
|
|
185
|
+
data: message,
|
|
186
|
+
type: "bytes"
|
|
187
|
+
});
|
|
188
|
+
return { signature: result.signature };
|
|
189
|
+
}
|
|
190
|
+
async function authenticate(serverUrl, did, signer, address) {
|
|
191
|
+
const challenge = await requestChallenge(serverUrl, did);
|
|
192
|
+
const timestamp = Date.now();
|
|
193
|
+
const origin = typeof window !== "undefined" ? window.location.origin : "";
|
|
194
|
+
const network = extractNetworkId(did);
|
|
195
|
+
const { signature } = await signChallenge(
|
|
196
|
+
challenge.nonce,
|
|
197
|
+
timestamp,
|
|
198
|
+
origin,
|
|
199
|
+
signer,
|
|
200
|
+
address,
|
|
201
|
+
network
|
|
202
|
+
);
|
|
203
|
+
const response = await fetch(`${serverUrl}/auth/did/verify`, {
|
|
204
|
+
method: "POST",
|
|
205
|
+
headers: { "Content-Type": "application/json" },
|
|
206
|
+
body: JSON.stringify({
|
|
207
|
+
did,
|
|
208
|
+
nonce: challenge.nonce,
|
|
209
|
+
signature,
|
|
210
|
+
timestamp,
|
|
211
|
+
origin
|
|
212
|
+
})
|
|
213
|
+
});
|
|
214
|
+
if (!response.ok) {
|
|
215
|
+
const error = await response.json().catch(() => ({ error: "Verification failed" }));
|
|
216
|
+
throw new Error(error.error || "DID authentication failed");
|
|
217
|
+
}
|
|
218
|
+
return await response.json();
|
|
219
|
+
}
|
|
220
|
+
export {
|
|
221
|
+
authenticate,
|
|
222
|
+
constructMessage,
|
|
223
|
+
extractNetworkId,
|
|
224
|
+
generateChallenge,
|
|
225
|
+
issueToken,
|
|
226
|
+
requestChallenge,
|
|
227
|
+
signChallenge,
|
|
228
|
+
validateToken,
|
|
229
|
+
validateTokenLite,
|
|
230
|
+
verifyDIDSignature
|
|
231
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@civiwave/vineyard-id",
|
|
3
|
+
"description": "Sovereign Identity SDK for Civiwave — DID authentication, resolution, and verifiable credential verification against the civiwave identity-hub",
|
|
4
|
+
"version": "0.1.1",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.cjs",
|
|
7
|
+
"module": "dist/index.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"require": "./dist/index.cjs"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsup",
|
|
18
|
+
"typecheck": "tsc --noEmit",
|
|
19
|
+
"test": "echo \"No tests yet\""
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@polkadot/util": "^14.0.1",
|
|
23
|
+
"@polkadot/util-crypto": "^14.0.1",
|
|
24
|
+
"jsonwebtoken": "^9.0.2"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@types/jsonwebtoken": "9.0.5",
|
|
28
|
+
"@types/node": "^20.11.30",
|
|
29
|
+
"tsup": "^8.5.1",
|
|
30
|
+
"typescript": "^5.4.5"
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"dist"
|
|
34
|
+
]
|
|
35
|
+
}
|