@ixo/ucan 1.0.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/.eslintrc.js +9 -0
- package/.prettierignore +3 -0
- package/.prettierrc.js +4 -0
- package/.turbo/turbo-build.log +4 -0
- package/CHANGELOG.md +0 -0
- package/README.md +189 -0
- package/dist/capabilities/capability.d.ts +33 -0
- package/dist/capabilities/capability.d.ts.map +1 -0
- package/dist/capabilities/capability.js +53 -0
- package/dist/capabilities/capability.js.map +1 -0
- package/dist/client/create-client.d.ts +33 -0
- package/dist/client/create-client.d.ts.map +1 -0
- package/dist/client/create-client.js +104 -0
- package/dist/client/create-client.js.map +1 -0
- package/dist/did/ixo-resolver.d.ts +8 -0
- package/dist/did/ixo-resolver.d.ts.map +1 -0
- package/dist/did/ixo-resolver.js +162 -0
- package/dist/did/ixo-resolver.js.map +1 -0
- package/dist/did/utils.d.ts +4 -0
- package/dist/did/utils.d.ts.map +1 -0
- package/dist/did/utils.js +85 -0
- package/dist/did/utils.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/store/memory.d.ts +25 -0
- package/dist/store/memory.d.ts.map +1 -0
- package/dist/store/memory.js +71 -0
- package/dist/store/memory.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/types.d.ts +29 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/validator/validator.d.ts +29 -0
- package/dist/validator/validator.d.ts.map +1 -0
- package/dist/validator/validator.js +179 -0
- package/dist/validator/validator.js.map +1 -0
- package/jest.config.js +3 -0
- package/package.json +78 -0
- package/scripts/test-ucan.ts +457 -0
- package/src/capabilities/capability.ts +244 -0
- package/src/client/create-client.ts +329 -0
- package/src/did/ixo-resolver.ts +325 -0
- package/src/did/utils.ts +141 -0
- package/src/index.ts +135 -0
- package/src/store/memory.ts +194 -0
- package/src/types.ts +108 -0
- package/src/validator/validator.ts +399 -0
- package/tsconfig.json +18 -0
package/src/did/utils.ts
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// Base58 Encoding/Decoding (Bitcoin alphabet)
|
|
3
|
+
// =============================================================================
|
|
4
|
+
|
|
5
|
+
const BASE58_ALPHABET =
|
|
6
|
+
'123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
|
|
7
|
+
|
|
8
|
+
// Create lookup table for faster decoding
|
|
9
|
+
const BASE58_MAP: Record<string, number> = {};
|
|
10
|
+
for (let i = 0; i < BASE58_ALPHABET.length; i++) {
|
|
11
|
+
BASE58_MAP[BASE58_ALPHABET[i]!] = i;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Decode a Base58-encoded string to bytes
|
|
16
|
+
*
|
|
17
|
+
* Algorithm: Treat input as base-58 number, convert to base-256
|
|
18
|
+
*/
|
|
19
|
+
export function base58Decode(str: string): Uint8Array {
|
|
20
|
+
if (str.length === 0) {
|
|
21
|
+
return new Uint8Array(0);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Count leading '1's (which represent leading zero bytes)
|
|
25
|
+
let leadingZeros = 0;
|
|
26
|
+
for (const char of str) {
|
|
27
|
+
if (char === '1') {
|
|
28
|
+
leadingZeros++;
|
|
29
|
+
} else {
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Allocate enough space for the result
|
|
35
|
+
// Base58 uses ~5.86 bits per character, so we need at most ceil(len * log(58) / log(256)) bytes
|
|
36
|
+
const size = Math.ceil((str.length * Math.log(58)) / Math.log(256));
|
|
37
|
+
const bytes = new Uint8Array(size);
|
|
38
|
+
|
|
39
|
+
// Process each character
|
|
40
|
+
for (const char of str) {
|
|
41
|
+
const value = BASE58_MAP[char];
|
|
42
|
+
if (value === undefined) {
|
|
43
|
+
throw new Error(`Invalid Base58 character: ${char}`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Multiply existing bytes by 58 and add the new value
|
|
47
|
+
let carry = value;
|
|
48
|
+
for (let i = size - 1; i >= 0; i--) {
|
|
49
|
+
const current = bytes[i]! * 58 + carry;
|
|
50
|
+
bytes[i] = current % 256;
|
|
51
|
+
carry = Math.floor(current / 256);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Find where the actual data starts (skip leading zeros in result)
|
|
56
|
+
let start = 0;
|
|
57
|
+
while (start < bytes.length && bytes[start] === 0) {
|
|
58
|
+
start++;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Combine leading zero bytes with the decoded data
|
|
62
|
+
const result = new Uint8Array(leadingZeros + (bytes.length - start));
|
|
63
|
+
// Leading zeros are already 0 in a new Uint8Array
|
|
64
|
+
result.set(bytes.subarray(start), leadingZeros);
|
|
65
|
+
|
|
66
|
+
return result;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Encode bytes to Base58 string
|
|
71
|
+
*
|
|
72
|
+
* Algorithm: Treat input as base-256 number, convert to base-58
|
|
73
|
+
*/
|
|
74
|
+
export function base58Encode(bytes: Uint8Array): string {
|
|
75
|
+
if (bytes.length === 0) {
|
|
76
|
+
return '';
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Count leading zeros
|
|
80
|
+
let leadingZeros = 0;
|
|
81
|
+
for (const byte of bytes) {
|
|
82
|
+
if (byte === 0) {
|
|
83
|
+
leadingZeros++;
|
|
84
|
+
} else {
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Allocate enough space for the result
|
|
90
|
+
// We need at most ceil(len * log(256) / log(58)) digits
|
|
91
|
+
const size = Math.ceil((bytes.length * Math.log(256)) / Math.log(58));
|
|
92
|
+
const digits = new Uint8Array(size);
|
|
93
|
+
|
|
94
|
+
// Process each byte
|
|
95
|
+
for (const byte of bytes) {
|
|
96
|
+
let carry = byte;
|
|
97
|
+
for (let i = size - 1; i >= 0; i--) {
|
|
98
|
+
const current = digits[i]! * 256 + carry;
|
|
99
|
+
digits[i] = current % 58;
|
|
100
|
+
carry = Math.floor(current / 58);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Find where the actual data starts
|
|
105
|
+
let start = 0;
|
|
106
|
+
while (start < digits.length && digits[start] === 0) {
|
|
107
|
+
start++;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Build result string
|
|
111
|
+
let result = '1'.repeat(leadingZeros);
|
|
112
|
+
for (let i = start; i < digits.length; i++) {
|
|
113
|
+
result += BASE58_ALPHABET[digits[i]!];
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return result;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// =============================================================================
|
|
120
|
+
// Hex Encoding/Decoding
|
|
121
|
+
// =============================================================================
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Decode a hex string to bytes
|
|
125
|
+
*/
|
|
126
|
+
export function hexDecode(hex: string): Uint8Array {
|
|
127
|
+
// Remove optional 0x prefix
|
|
128
|
+
const cleanHex = hex.startsWith('0x') ? hex.slice(2) : hex;
|
|
129
|
+
|
|
130
|
+
if (cleanHex.length % 2 !== 0) {
|
|
131
|
+
throw new Error('Invalid hex string: odd length');
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const bytes = new Uint8Array(cleanHex.length / 2);
|
|
135
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
136
|
+
bytes[i] = parseInt(cleanHex.slice(i * 2, i * 2 + 2), 16);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return bytes;
|
|
140
|
+
}
|
|
141
|
+
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview @ixo/ucan - UCAN authorization for any service
|
|
3
|
+
*
|
|
4
|
+
* This package provides UCAN (User Controlled Authorization Networks) support
|
|
5
|
+
* built on top of the battle-tested ucanto library.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Generic capability definitions (define your own)
|
|
9
|
+
* - Framework-agnostic validator (works with Express, Fastify, etc.)
|
|
10
|
+
* - Client helpers for creating delegations and invocations
|
|
11
|
+
* - did:ixo resolution via IXO blockchain indexer (optional)
|
|
12
|
+
* - In-memory invocation store for replay protection
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* // 1. Define your capabilities
|
|
17
|
+
* import { defineCapability, createUCANValidator, generateKeypair } from '@ixo/ucan';
|
|
18
|
+
*
|
|
19
|
+
* const EmployeesRead = defineCapability({
|
|
20
|
+
* can: 'employees/read',
|
|
21
|
+
* protocol: 'myapp:'
|
|
22
|
+
* });
|
|
23
|
+
*
|
|
24
|
+
* // 2. Create validator
|
|
25
|
+
* const validator = createUCANValidator({
|
|
26
|
+
* serverDid: 'did:key:z6Mk...',
|
|
27
|
+
* rootIssuers: ['did:key:z6MkAdmin...'],
|
|
28
|
+
* });
|
|
29
|
+
*
|
|
30
|
+
* // 3. Validate in your route (any framework)
|
|
31
|
+
* app.post('/protected', async (req, res) => {
|
|
32
|
+
* const result = await validator.validate(req.body.invocation, {
|
|
33
|
+
* can: 'employees/read',
|
|
34
|
+
* with: 'myapp:employees'
|
|
35
|
+
* });
|
|
36
|
+
*
|
|
37
|
+
* if (!result.ok) {
|
|
38
|
+
* return res.status(403).json({ error: result.error });
|
|
39
|
+
* }
|
|
40
|
+
*
|
|
41
|
+
* res.json({ employees: [...] });
|
|
42
|
+
* });
|
|
43
|
+
* ```
|
|
44
|
+
*
|
|
45
|
+
* @packageDocumentation
|
|
46
|
+
*/
|
|
47
|
+
|
|
48
|
+
// =============================================================================
|
|
49
|
+
// Re-export ucanto packages for advanced usage
|
|
50
|
+
// =============================================================================
|
|
51
|
+
|
|
52
|
+
export * as UcantoServer from '@ucanto/server';
|
|
53
|
+
export * as UcantoClient from '@ucanto/client';
|
|
54
|
+
export * as UcantoValidator from '@ucanto/validator';
|
|
55
|
+
export * as UcantoPrincipal from '@ucanto/principal';
|
|
56
|
+
export { ed25519, Verifier } from '@ucanto/principal';
|
|
57
|
+
|
|
58
|
+
// =============================================================================
|
|
59
|
+
// Types
|
|
60
|
+
// =============================================================================
|
|
61
|
+
|
|
62
|
+
export type {
|
|
63
|
+
IxoDID,
|
|
64
|
+
KeyDID,
|
|
65
|
+
SupportedDID,
|
|
66
|
+
DIDKeyResolutionResult,
|
|
67
|
+
DIDKeyResolver,
|
|
68
|
+
InvocationStore,
|
|
69
|
+
ValidationResult,
|
|
70
|
+
SerializedInvocation,
|
|
71
|
+
} from './types.js';
|
|
72
|
+
|
|
73
|
+
// =============================================================================
|
|
74
|
+
// Capability Definition
|
|
75
|
+
// =============================================================================
|
|
76
|
+
|
|
77
|
+
export {
|
|
78
|
+
defineCapability,
|
|
79
|
+
Schema,
|
|
80
|
+
type DefineCapabilityOptions,
|
|
81
|
+
} from './capabilities/capability.js';
|
|
82
|
+
|
|
83
|
+
// =============================================================================
|
|
84
|
+
// Validator
|
|
85
|
+
// =============================================================================
|
|
86
|
+
|
|
87
|
+
export {
|
|
88
|
+
createUCANValidator,
|
|
89
|
+
type CreateValidatorOptions,
|
|
90
|
+
type ValidateResult,
|
|
91
|
+
type UCANValidator,
|
|
92
|
+
} from './validator/validator.js';
|
|
93
|
+
|
|
94
|
+
// =============================================================================
|
|
95
|
+
// Client Helpers (for creating delegations and invocations)
|
|
96
|
+
// =============================================================================
|
|
97
|
+
|
|
98
|
+
export {
|
|
99
|
+
generateKeypair,
|
|
100
|
+
parseSigner,
|
|
101
|
+
signerFromMnemonic,
|
|
102
|
+
createDelegation,
|
|
103
|
+
createInvocation,
|
|
104
|
+
serializeInvocation,
|
|
105
|
+
serializeDelegation,
|
|
106
|
+
parseDelegation,
|
|
107
|
+
type Signer,
|
|
108
|
+
type Delegation,
|
|
109
|
+
type Capability,
|
|
110
|
+
} from './client/create-client.js';
|
|
111
|
+
|
|
112
|
+
// =============================================================================
|
|
113
|
+
// DID Resolution (optional, for did:ixo support)
|
|
114
|
+
// =============================================================================
|
|
115
|
+
|
|
116
|
+
export {
|
|
117
|
+
createIxoDIDResolver,
|
|
118
|
+
createCompositeDIDResolver,
|
|
119
|
+
type IxoDIDResolverConfig,
|
|
120
|
+
} from './did/ixo-resolver.js';
|
|
121
|
+
|
|
122
|
+
// =============================================================================
|
|
123
|
+
// Store (for replay protection)
|
|
124
|
+
// =============================================================================
|
|
125
|
+
|
|
126
|
+
export {
|
|
127
|
+
InMemoryInvocationStore,
|
|
128
|
+
createInvocationStore,
|
|
129
|
+
} from './store/memory.js';
|
|
130
|
+
|
|
131
|
+
// =============================================================================
|
|
132
|
+
// Version
|
|
133
|
+
// =============================================================================
|
|
134
|
+
|
|
135
|
+
export const VERSION = '1.0.0';
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview In-memory invocation store for replay protection
|
|
3
|
+
*
|
|
4
|
+
* This module provides a simple in-memory implementation of the InvocationStore
|
|
5
|
+
* interface for tracking used invocation CIDs to prevent replay attacks.
|
|
6
|
+
*
|
|
7
|
+
* For production use with multiple instances, consider using a distributed
|
|
8
|
+
* store like Redis.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { InvocationStore } from '../types.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Entry in the invocation store
|
|
15
|
+
*/
|
|
16
|
+
interface StoreEntry {
|
|
17
|
+
/** Timestamp when this entry expires */
|
|
18
|
+
expiresAt: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* In-memory implementation of InvocationStore for replay protection
|
|
23
|
+
*
|
|
24
|
+
* Features:
|
|
25
|
+
* - Automatic TTL-based expiration
|
|
26
|
+
* - Periodic cleanup of expired entries
|
|
27
|
+
* - Thread-safe (single-threaded JS)
|
|
28
|
+
*
|
|
29
|
+
* Limitations:
|
|
30
|
+
* - Data is lost on process restart
|
|
31
|
+
* - Not suitable for distributed deployments
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```typescript
|
|
35
|
+
* const store = new InMemoryInvocationStore();
|
|
36
|
+
*
|
|
37
|
+
* // Check if invocation was already used
|
|
38
|
+
* if (await store.has(invocationCid)) {
|
|
39
|
+
* throw new Error('Replay attack detected');
|
|
40
|
+
* }
|
|
41
|
+
*
|
|
42
|
+
* // Mark invocation as used
|
|
43
|
+
* await store.add(invocationCid);
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
export class InMemoryInvocationStore implements InvocationStore {
|
|
47
|
+
private store = new Map<string, StoreEntry>();
|
|
48
|
+
private cleanupInterval: ReturnType<typeof setInterval> | null = null;
|
|
49
|
+
|
|
50
|
+
/** Default TTL: 24 hours */
|
|
51
|
+
private readonly defaultTtlMs: number;
|
|
52
|
+
|
|
53
|
+
/** Cleanup interval: 1 hour */
|
|
54
|
+
private readonly cleanupIntervalMs: number;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Create a new in-memory invocation store
|
|
58
|
+
*
|
|
59
|
+
* @param options - Configuration options
|
|
60
|
+
* @param options.defaultTtlMs - Default TTL for entries (default: 24 hours)
|
|
61
|
+
* @param options.cleanupIntervalMs - Interval for cleanup (default: 1 hour)
|
|
62
|
+
* @param options.enableAutoCleanup - Whether to enable automatic cleanup (default: true)
|
|
63
|
+
*/
|
|
64
|
+
constructor(
|
|
65
|
+
options: {
|
|
66
|
+
defaultTtlMs?: number;
|
|
67
|
+
cleanupIntervalMs?: number;
|
|
68
|
+
enableAutoCleanup?: boolean;
|
|
69
|
+
} = {},
|
|
70
|
+
) {
|
|
71
|
+
this.defaultTtlMs = options.defaultTtlMs ?? 24 * 60 * 60 * 1000; // 24 hours
|
|
72
|
+
this.cleanupIntervalMs = options.cleanupIntervalMs ?? 60 * 60 * 1000; // 1 hour
|
|
73
|
+
|
|
74
|
+
if (options.enableAutoCleanup !== false) {
|
|
75
|
+
this.startAutoCleanup();
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Check if an invocation CID has already been used
|
|
81
|
+
*
|
|
82
|
+
* @param cid - The CID of the invocation
|
|
83
|
+
* @returns True if the CID has been used and is not expired
|
|
84
|
+
*/
|
|
85
|
+
async has(cid: string): Promise<boolean> {
|
|
86
|
+
const entry = this.store.get(cid);
|
|
87
|
+
if (!entry) {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Check if expired
|
|
92
|
+
if (Date.now() > entry.expiresAt) {
|
|
93
|
+
this.store.delete(cid);
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Mark an invocation CID as used
|
|
102
|
+
*
|
|
103
|
+
* @param cid - The CID of the invocation
|
|
104
|
+
* @param ttlMs - Time-to-live in milliseconds (default: 24 hours)
|
|
105
|
+
*/
|
|
106
|
+
async add(cid: string, ttlMs?: number): Promise<void> {
|
|
107
|
+
const ttl = ttlMs ?? this.defaultTtlMs;
|
|
108
|
+
this.store.set(cid, {
|
|
109
|
+
expiresAt: Date.now() + ttl,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Remove all expired entries from the store
|
|
115
|
+
*/
|
|
116
|
+
async cleanup(): Promise<void> {
|
|
117
|
+
const now = Date.now();
|
|
118
|
+
let cleaned = 0;
|
|
119
|
+
|
|
120
|
+
for (const [cid, entry] of this.store.entries()) {
|
|
121
|
+
if (now > entry.expiresAt) {
|
|
122
|
+
this.store.delete(cid);
|
|
123
|
+
cleaned++;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (cleaned > 0) {
|
|
128
|
+
console.log(`[InMemoryInvocationStore] Cleaned up ${cleaned} expired entries`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Get the current size of the store
|
|
134
|
+
*/
|
|
135
|
+
get size(): number {
|
|
136
|
+
return this.store.size;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Clear all entries from the store
|
|
141
|
+
*/
|
|
142
|
+
clear(): void {
|
|
143
|
+
this.store.clear();
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Start automatic cleanup interval
|
|
148
|
+
*/
|
|
149
|
+
private startAutoCleanup(): void {
|
|
150
|
+
if (this.cleanupInterval) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
this.cleanupInterval = setInterval(() => {
|
|
155
|
+
void this.cleanup();
|
|
156
|
+
}, this.cleanupIntervalMs);
|
|
157
|
+
|
|
158
|
+
// Don't prevent process from exiting
|
|
159
|
+
if (this.cleanupInterval.unref) {
|
|
160
|
+
this.cleanupInterval.unref();
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Stop automatic cleanup and release resources
|
|
166
|
+
*/
|
|
167
|
+
destroy(): void {
|
|
168
|
+
if (this.cleanupInterval) {
|
|
169
|
+
clearInterval(this.cleanupInterval);
|
|
170
|
+
this.cleanupInterval = null;
|
|
171
|
+
}
|
|
172
|
+
this.store.clear();
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Create an invocation store instance
|
|
178
|
+
* Factory function for easier testing and dependency injection
|
|
179
|
+
*
|
|
180
|
+
* @param options - Store configuration
|
|
181
|
+
* @returns An InvocationStore implementation
|
|
182
|
+
*/
|
|
183
|
+
export function createInvocationStore(options?: {
|
|
184
|
+
defaultTtlMs?: number;
|
|
185
|
+
cleanupIntervalMs?: number;
|
|
186
|
+
enableAutoCleanup?: boolean;
|
|
187
|
+
}): InvocationStore {
|
|
188
|
+
return new InMemoryInvocationStore(options);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// TODO: Add Redis implementation for distributed deployments
|
|
192
|
+
// TODO: Add SQLite implementation for persistence across restarts
|
|
193
|
+
// TODO: Add metrics/monitoring for store size and cleanup operations
|
|
194
|
+
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Core type definitions for @ixo/ucan
|
|
3
|
+
*
|
|
4
|
+
* This module provides type definitions that extend ucanto's types
|
|
5
|
+
* for general use in any service that needs UCAN authorization.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { DID, Capability as UcantoCapability } from '@ucanto/interface';
|
|
9
|
+
|
|
10
|
+
// =============================================================================
|
|
11
|
+
// DID Types
|
|
12
|
+
// =============================================================================
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* IXO DID type (for IXO blockchain identities)
|
|
16
|
+
*/
|
|
17
|
+
export type IxoDID = `did:ixo:${string}`;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Key DID type (self-describing public key DIDs)
|
|
21
|
+
*/
|
|
22
|
+
export type KeyDID = `did:key:${string}`;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* DIDs supported by this package
|
|
26
|
+
*/
|
|
27
|
+
export type SupportedDID = IxoDID | KeyDID;
|
|
28
|
+
|
|
29
|
+
// =============================================================================
|
|
30
|
+
// DID Resolution
|
|
31
|
+
// =============================================================================
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Result of DID key resolution
|
|
35
|
+
*/
|
|
36
|
+
export interface DIDKeyResolutionResult {
|
|
37
|
+
/** Array of did:key identifiers that can verify signatures for this DID */
|
|
38
|
+
keys: KeyDID[];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* DID key resolver function type
|
|
43
|
+
* Takes a DID and returns the associated did:key identifiers
|
|
44
|
+
*/
|
|
45
|
+
export type DIDKeyResolver = (
|
|
46
|
+
did: DID,
|
|
47
|
+
) => Promise<
|
|
48
|
+
{ ok: KeyDID[] } | { error: { name: string; did: string; message: string } }
|
|
49
|
+
>;
|
|
50
|
+
|
|
51
|
+
// =============================================================================
|
|
52
|
+
// Invocation Store (Replay Protection)
|
|
53
|
+
// =============================================================================
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Invocation store for replay protection
|
|
57
|
+
*
|
|
58
|
+
* Implementations can use in-memory, Redis, database, etc.
|
|
59
|
+
*/
|
|
60
|
+
export interface InvocationStore {
|
|
61
|
+
/**
|
|
62
|
+
* Check if an invocation CID has already been used
|
|
63
|
+
* @param cid - The CID of the invocation
|
|
64
|
+
*/
|
|
65
|
+
has(cid: string): Promise<boolean>;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Mark an invocation CID as used
|
|
69
|
+
* @param cid - The CID of the invocation
|
|
70
|
+
* @param ttlMs - Time-to-live in milliseconds (for cleanup)
|
|
71
|
+
*/
|
|
72
|
+
add(cid: string, ttlMs?: number): Promise<void>;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Remove expired entries (optional cleanup method)
|
|
76
|
+
*/
|
|
77
|
+
cleanup?(): Promise<void>;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// =============================================================================
|
|
81
|
+
// Validation
|
|
82
|
+
// =============================================================================
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Result of UCAN validation
|
|
86
|
+
*/
|
|
87
|
+
export interface ValidationResult {
|
|
88
|
+
/** Whether the validation succeeded */
|
|
89
|
+
valid: boolean;
|
|
90
|
+
|
|
91
|
+
/** Error message if validation failed */
|
|
92
|
+
error?: string;
|
|
93
|
+
|
|
94
|
+
/** The DID of the invoker (if valid) */
|
|
95
|
+
invokerDid?: string;
|
|
96
|
+
|
|
97
|
+
/** The validated capability (if valid) */
|
|
98
|
+
capability?: UcantoCapability;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// =============================================================================
|
|
102
|
+
// Client Configuration
|
|
103
|
+
// =============================================================================
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Serialized invocation that can be sent in HTTP requests
|
|
107
|
+
*/
|
|
108
|
+
export type SerializedInvocation = string;
|