@cmdoss/memwal-sdk 0.6.2 → 0.8.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/ARCHITECTURE.md +547 -547
- package/BENCHMARKS.md +238 -238
- package/README.md +310 -181
- package/dist/ai-sdk/tools.d.ts +2 -2
- package/dist/ai-sdk/tools.js +2 -2
- package/dist/client/ClientMemoryManager.js +2 -2
- package/dist/client/ClientMemoryManager.js.map +1 -1
- package/dist/client/PersonalDataWallet.d.ts.map +1 -1
- package/dist/client/SimplePDWClient.d.ts +29 -1
- package/dist/client/SimplePDWClient.d.ts.map +1 -1
- package/dist/client/SimplePDWClient.js +45 -13
- package/dist/client/SimplePDWClient.js.map +1 -1
- package/dist/client/namespaces/EmbeddingsNamespace.d.ts +1 -1
- package/dist/client/namespaces/EmbeddingsNamespace.js +1 -1
- package/dist/client/namespaces/MemoryNamespace.d.ts +31 -0
- package/dist/client/namespaces/MemoryNamespace.d.ts.map +1 -1
- package/dist/client/namespaces/MemoryNamespace.js +272 -39
- package/dist/client/namespaces/MemoryNamespace.js.map +1 -1
- package/dist/client/namespaces/consolidated/AINamespace.d.ts +2 -2
- package/dist/client/namespaces/consolidated/AINamespace.js +2 -2
- package/dist/client/namespaces/consolidated/BlockchainNamespace.d.ts +12 -2
- package/dist/client/namespaces/consolidated/BlockchainNamespace.d.ts.map +1 -1
- package/dist/client/namespaces/consolidated/BlockchainNamespace.js +62 -4
- package/dist/client/namespaces/consolidated/BlockchainNamespace.js.map +1 -1
- package/dist/client/namespaces/consolidated/StorageNamespace.d.ts +67 -2
- package/dist/client/namespaces/consolidated/StorageNamespace.d.ts.map +1 -1
- package/dist/client/namespaces/consolidated/StorageNamespace.js +549 -16
- package/dist/client/namespaces/consolidated/StorageNamespace.js.map +1 -1
- package/dist/config/ConfigurationHelper.js +61 -61
- package/dist/config/defaults.js +2 -2
- package/dist/config/defaults.js.map +1 -1
- package/dist/graph/GraphService.js +21 -21
- package/dist/graph/GraphService.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/seal/EncryptionService.d.ts +9 -5
- package/dist/infrastructure/seal/EncryptionService.d.ts.map +1 -1
- package/dist/infrastructure/seal/EncryptionService.js +37 -15
- package/dist/infrastructure/seal/EncryptionService.js.map +1 -1
- package/dist/infrastructure/seal/SealService.d.ts +13 -5
- package/dist/infrastructure/seal/SealService.d.ts.map +1 -1
- package/dist/infrastructure/seal/SealService.js +36 -34
- package/dist/infrastructure/seal/SealService.js.map +1 -1
- package/dist/langchain/createPDWRAG.js +30 -30
- package/dist/retrieval/MemoryDecryptionPipeline.d.ts.map +1 -1
- package/dist/retrieval/MemoryDecryptionPipeline.js +2 -1
- package/dist/retrieval/MemoryDecryptionPipeline.js.map +1 -1
- package/dist/retrieval/MemoryRetrievalService.d.ts +31 -0
- package/dist/retrieval/MemoryRetrievalService.d.ts.map +1 -1
- package/dist/retrieval/MemoryRetrievalService.js +44 -4
- package/dist/retrieval/MemoryRetrievalService.js.map +1 -1
- package/dist/services/CapabilityService.d.ts.map +1 -1
- package/dist/services/CapabilityService.js +30 -14
- package/dist/services/CapabilityService.js.map +1 -1
- package/dist/services/CrossContextPermissionService.d.ts.map +1 -1
- package/dist/services/CrossContextPermissionService.js +9 -7
- package/dist/services/CrossContextPermissionService.js.map +1 -1
- package/dist/services/EmbeddingService.d.ts +28 -1
- package/dist/services/EmbeddingService.d.ts.map +1 -1
- package/dist/services/EmbeddingService.js +54 -0
- package/dist/services/EmbeddingService.js.map +1 -1
- package/dist/services/EncryptionService.d.ts.map +1 -1
- package/dist/services/EncryptionService.js +6 -5
- package/dist/services/EncryptionService.js.map +1 -1
- package/dist/services/GeminiAIService.js +309 -309
- package/dist/services/IndexManager.d.ts +5 -1
- package/dist/services/IndexManager.d.ts.map +1 -1
- package/dist/services/IndexManager.js +17 -40
- package/dist/services/IndexManager.js.map +1 -1
- package/dist/services/QueryService.js +1 -1
- package/dist/services/QueryService.js.map +1 -1
- package/dist/services/StorageService.d.ts +11 -0
- package/dist/services/StorageService.d.ts.map +1 -1
- package/dist/services/StorageService.js +73 -10
- package/dist/services/StorageService.js.map +1 -1
- package/dist/services/TransactionService.d.ts +20 -0
- package/dist/services/TransactionService.d.ts.map +1 -1
- package/dist/services/TransactionService.js +43 -0
- package/dist/services/TransactionService.js.map +1 -1
- package/dist/services/ViewService.js +2 -2
- package/dist/services/ViewService.js.map +1 -1
- package/dist/services/storage/QuiltBatchManager.d.ts +101 -1
- package/dist/services/storage/QuiltBatchManager.d.ts.map +1 -1
- package/dist/services/storage/QuiltBatchManager.js +410 -20
- package/dist/services/storage/QuiltBatchManager.js.map +1 -1
- package/dist/services/storage/index.d.ts +1 -1
- package/dist/services/storage/index.d.ts.map +1 -1
- package/dist/services/storage/index.js.map +1 -1
- package/dist/utils/LRUCache.d.ts +106 -0
- package/dist/utils/LRUCache.d.ts.map +1 -0
- package/dist/utils/LRUCache.js +281 -0
- package/dist/utils/LRUCache.js.map +1 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +2 -0
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/memoryIndexOnChain.d.ts +212 -0
- package/dist/utils/memoryIndexOnChain.d.ts.map +1 -0
- package/dist/utils/memoryIndexOnChain.js +312 -0
- package/dist/utils/memoryIndexOnChain.js.map +1 -0
- package/dist/utils/rebuildIndexNode.d.ts +29 -0
- package/dist/utils/rebuildIndexNode.d.ts.map +1 -1
- package/dist/utils/rebuildIndexNode.js +366 -98
- package/dist/utils/rebuildIndexNode.js.map +1 -1
- package/dist/vector/HnswWasmService.d.ts +20 -5
- package/dist/vector/HnswWasmService.d.ts.map +1 -1
- package/dist/vector/HnswWasmService.js +73 -40
- package/dist/vector/HnswWasmService.js.map +1 -1
- package/dist/vector/IHnswService.d.ts +10 -1
- package/dist/vector/IHnswService.d.ts.map +1 -1
- package/dist/vector/IHnswService.js.map +1 -1
- package/dist/vector/NodeHnswService.d.ts +16 -0
- package/dist/vector/NodeHnswService.d.ts.map +1 -1
- package/dist/vector/NodeHnswService.js +84 -5
- package/dist/vector/NodeHnswService.js.map +1 -1
- package/dist/vector/createHnswService.d.ts +1 -1
- package/dist/vector/createHnswService.js +1 -1
- package/dist/vector/index.d.ts +1 -1
- package/dist/vector/index.js +1 -1
- package/package.json +157 -157
- package/src/access/PermissionService.ts +635 -635
- package/src/aggregation/AggregationService.ts +389 -389
- package/src/ai-sdk/PDWVectorStore.ts +715 -715
- package/src/ai-sdk/index.ts +65 -65
- package/src/ai-sdk/tools.ts +460 -460
- package/src/ai-sdk/types.ts +404 -404
- package/src/batch/BatchManager.ts +597 -597
- package/src/batch/BatchingService.ts +429 -429
- package/src/batch/MemoryProcessingCache.ts +492 -492
- package/src/batch/index.ts +30 -30
- package/src/browser.ts +200 -200
- package/src/client/ClientMemoryManager.ts +987 -987
- package/src/client/PersonalDataWallet.ts +345 -345
- package/src/client/SimplePDWClient.ts +1289 -1222
- package/src/client/factory.ts +154 -154
- package/src/client/namespaces/AnalyticsNamespace.ts +377 -377
- package/src/client/namespaces/BatchNamespace.ts +356 -356
- package/src/client/namespaces/CacheNamespace.ts +123 -123
- package/src/client/namespaces/CapabilityNamespace.ts +217 -217
- package/src/client/namespaces/ClassifyNamespace.ts +169 -169
- package/src/client/namespaces/ContextNamespace.ts +297 -297
- package/src/client/namespaces/EmbeddingsNamespace.ts +99 -99
- package/src/client/namespaces/EncryptionNamespace.ts +221 -221
- package/src/client/namespaces/GraphNamespace.ts +468 -468
- package/src/client/namespaces/IndexNamespace.ts +361 -361
- package/src/client/namespaces/MemoryNamespace.ts +1422 -1135
- package/src/client/namespaces/PermissionsNamespace.ts +254 -254
- package/src/client/namespaces/PipelineNamespace.ts +220 -220
- package/src/client/namespaces/SearchNamespace.ts +1049 -1049
- package/src/client/namespaces/StorageNamespace.ts +458 -458
- package/src/client/namespaces/TxNamespace.ts +260 -260
- package/src/client/namespaces/WalletNamespace.ts +243 -243
- package/src/client/namespaces/consolidated/AINamespace.ts +449 -449
- package/src/client/namespaces/consolidated/BlockchainNamespace.ts +607 -546
- package/src/client/namespaces/consolidated/SecurityNamespace.ts +648 -648
- package/src/client/namespaces/consolidated/StorageNamespace.ts +1141 -497
- package/src/client/namespaces/consolidated/index.ts +39 -39
- package/src/client/signers/KeypairSigner.ts +108 -108
- package/src/client/signers/UnifiedSigner.ts +110 -110
- package/src/client/signers/WalletAdapterSigner.ts +159 -159
- package/src/client/signers/index.ts +26 -26
- package/src/config/ConfigurationHelper.ts +412 -412
- package/src/config/defaults.ts +51 -51
- package/src/config/index.ts +8 -8
- package/src/config/validation.ts +70 -70
- package/src/core/index.ts +14 -14
- package/src/core/interfaces/IService.ts +307 -307
- package/src/core/interfaces/index.ts +8 -8
- package/src/core/types/capability.ts +297 -297
- package/src/core/types/index.ts +870 -870
- package/src/core/types/wallet.ts +270 -270
- package/src/core/types.ts +9 -9
- package/src/core/wallet.ts +222 -222
- package/src/embedding/index.ts +19 -19
- package/src/embedding/types.ts +357 -357
- package/src/errors/index.ts +602 -602
- package/src/errors/recovery.ts +461 -461
- package/src/errors/validation.ts +567 -567
- package/src/generated/pdw/capability.ts +319 -319
- package/src/graph/GraphService.ts +887 -887
- package/src/graph/KnowledgeGraphManager.ts +728 -728
- package/src/graph/index.ts +25 -25
- package/src/index.ts +498 -474
- package/src/infrastructure/index.ts +22 -22
- package/src/infrastructure/seal/EncryptionService.ts +628 -603
- package/src/infrastructure/seal/SealService.ts +613 -615
- package/src/infrastructure/seal/index.ts +9 -9
- package/src/infrastructure/sui/BlockchainManager.ts +627 -627
- package/src/infrastructure/sui/SuiService.ts +888 -888
- package/src/infrastructure/sui/index.ts +9 -9
- package/src/infrastructure/walrus/StorageManager.ts +604 -604
- package/src/infrastructure/walrus/WalrusStorageService.ts +612 -612
- package/src/infrastructure/walrus/index.ts +9 -9
- package/src/langchain/PDWEmbeddings.ts +145 -145
- package/src/langchain/PDWVectorStore.ts +456 -456
- package/src/langchain/createPDWRAG.ts +303 -303
- package/src/langchain/index.ts +47 -47
- package/src/permissions/ConsentRepository.browser.ts +249 -249
- package/src/permissions/ConsentRepository.ts +364 -364
- package/src/pipeline/MemoryPipeline.ts +862 -862
- package/src/pipeline/PipelineManager.ts +683 -683
- package/src/pipeline/index.ts +26 -26
- package/src/retrieval/AdvancedSearchService.ts +629 -629
- package/src/retrieval/MemoryAnalyticsService.ts +711 -711
- package/src/retrieval/MemoryDecryptionPipeline.ts +825 -824
- package/src/retrieval/MemoryRetrievalService.ts +904 -830
- package/src/retrieval/index.ts +42 -42
- package/src/services/BatchService.ts +352 -352
- package/src/services/CapabilityService.ts +464 -448
- package/src/services/ClassifierService.ts +465 -465
- package/src/services/CrossContextPermissionService.ts +486 -484
- package/src/services/EmbeddingService.ts +771 -706
- package/src/services/EncryptionService.ts +712 -711
- package/src/services/GeminiAIService.ts +753 -753
- package/src/services/IndexManager.ts +977 -1004
- package/src/services/MemoryIndexService.ts +1003 -1003
- package/src/services/MemoryService.ts +369 -369
- package/src/services/QueryService.ts +890 -890
- package/src/services/StorageService.ts +1182 -1111
- package/src/services/TransactionService.ts +838 -790
- package/src/services/VectorService.ts +462 -462
- package/src/services/ViewService.ts +484 -484
- package/src/services/index.ts +25 -25
- package/src/services/storage/BlobAttributesManager.ts +333 -333
- package/src/services/storage/KnowledgeGraphManager.ts +425 -425
- package/src/services/storage/MemorySearchManager.ts +387 -387
- package/src/services/storage/QuiltBatchManager.ts +1130 -660
- package/src/services/storage/WalrusMetadataManager.ts +268 -268
- package/src/services/storage/WalrusStorageManager.ts +287 -287
- package/src/services/storage/index.ts +57 -52
- package/src/types/index.ts +13 -13
- package/src/utils/LRUCache.ts +378 -0
- package/src/utils/index.ts +76 -68
- package/src/utils/memoryIndexOnChain.ts +507 -0
- package/src/utils/rebuildIndex.ts +290 -290
- package/src/utils/rebuildIndexNode.ts +771 -424
- package/src/vector/BrowserHnswIndexService.ts +758 -758
- package/src/vector/HnswWasmService.ts +731 -679
- package/src/vector/IHnswService.ts +233 -224
- package/src/vector/NodeHnswService.ts +833 -735
- package/src/vector/VectorManager.ts +478 -478
- package/src/vector/createHnswService.ts +135 -135
- package/src/vector/index.ts +56 -56
- package/src/wallet/ContextWalletService.ts +656 -656
- package/src/wallet/MainWalletService.ts +317 -317
|
@@ -1,636 +1,636 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* PermissionService - OAuth-style access control management
|
|
3
|
-
*
|
|
4
|
-
* Manages permissions for cross-app data access, including:
|
|
5
|
-
* - OAuth-style consent requests and grants
|
|
6
|
-
* - Permission validation and enforcement
|
|
7
|
-
* - On-chain access control integration
|
|
8
|
-
* - Permission auditing and revocation
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
// Use Web Crypto API (browser-compatible)
|
|
12
|
-
const randomUUID = (): string => crypto.randomUUID();
|
|
13
|
-
import { SuiClient } from '@mysten/sui/client';
|
|
14
|
-
import { Transaction } from '@mysten/sui/transactions';
|
|
15
|
-
import { normalizeSuiAddress } from '@mysten/sui/utils';
|
|
16
|
-
import type { Signer } from '@mysten/sui/cryptography';
|
|
17
|
-
|
|
18
|
-
import {
|
|
19
|
-
ConsentRequest,
|
|
20
|
-
ConsentRequestRecord,
|
|
21
|
-
ConsentStatus,
|
|
22
|
-
AccessGrant,
|
|
23
|
-
PermissionScope,
|
|
24
|
-
RequestConsentOptions,
|
|
25
|
-
GrantPermissionsOptions,
|
|
26
|
-
RevokePermissionsOptions
|
|
27
|
-
} from '../core/types/wallet.js';
|
|
28
|
-
import { ContextWalletService } from '../wallet/ContextWalletService.js';
|
|
29
|
-
import { CapabilityService } from '../services/CapabilityService.js';
|
|
30
|
-
import { CrossContextPermissionService } from '../services/CrossContextPermissionService';
|
|
31
|
-
import type { WalletAllowlistPermission } from '../services/CrossContextPermissionService';
|
|
32
|
-
import type { ConsentRepository } from '../permissions/ConsentRepository.js';
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Configuration for PermissionService
|
|
36
|
-
*/
|
|
37
|
-
export interface PermissionServiceConfig {
|
|
38
|
-
/** Sui client instance */
|
|
39
|
-
suiClient: SuiClient;
|
|
40
|
-
/** Package ID for Move contracts */
|
|
41
|
-
packageId: string;
|
|
42
|
-
/** Access registry ID for wallet allowlists */
|
|
43
|
-
accessRegistryId: string;
|
|
44
|
-
/**
|
|
45
|
-
* @deprecated Use capabilityService instead for capability-based architecture
|
|
46
|
-
* ContextWalletService for validation (legacy)
|
|
47
|
-
*/
|
|
48
|
-
contextWalletService?: ContextWalletService;
|
|
49
|
-
/** CapabilityService for capability-based validation (preferred) */
|
|
50
|
-
capabilityService?: CapabilityService;
|
|
51
|
-
/** Optional injected cross-context permission service */
|
|
52
|
-
crossContextPermissionService?: CrossContextPermissionService;
|
|
53
|
-
/** Optional repository for consent persistence */
|
|
54
|
-
consentRepository?: ConsentRepository;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* PermissionService handles OAuth-style access control
|
|
59
|
-
*/
|
|
60
|
-
export class PermissionService {
|
|
61
|
-
private suiClient: SuiClient;
|
|
62
|
-
private packageId: string;
|
|
63
|
-
/** @deprecated Use capabilityService instead */
|
|
64
|
-
private contextWalletService?: ContextWalletService;
|
|
65
|
-
private capabilityService?: CapabilityService;
|
|
66
|
-
private crossContextPermissions: CrossContextPermissionService;
|
|
67
|
-
private pendingConsents: Map<string, ConsentRequestRecord> = new Map();
|
|
68
|
-
private consentRepository?: ConsentRepository;
|
|
69
|
-
|
|
70
|
-
constructor(config: PermissionServiceConfig) {
|
|
71
|
-
this.suiClient = config.suiClient;
|
|
72
|
-
this.packageId = config.packageId;
|
|
73
|
-
this.contextWalletService = config.contextWalletService;
|
|
74
|
-
this.capabilityService = config.capabilityService;
|
|
75
|
-
this.consentRepository = config.consentRepository;
|
|
76
|
-
|
|
77
|
-
if (!config.accessRegistryId) {
|
|
78
|
-
throw new Error('PermissionService requires accessRegistryId for wallet-based permissions');
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
this.crossContextPermissions =
|
|
82
|
-
config.crossContextPermissionService ??
|
|
83
|
-
new CrossContextPermissionService(
|
|
84
|
-
{
|
|
85
|
-
packageId: this.packageId,
|
|
86
|
-
accessRegistryId: config.accessRegistryId,
|
|
87
|
-
},
|
|
88
|
-
this.suiClient,
|
|
89
|
-
);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Request user consent for accessing data
|
|
94
|
-
* @param options - Consent request options
|
|
95
|
-
* @returns Created consent request
|
|
96
|
-
*/
|
|
97
|
-
async requestConsent(options: RequestConsentOptions): Promise<ConsentRequestRecord> {
|
|
98
|
-
const requesterWallet = normalizeSuiAddress(options.requesterWallet);
|
|
99
|
-
const targetWallet = normalizeSuiAddress(options.targetWallet);
|
|
100
|
-
const now = Date.now();
|
|
101
|
-
|
|
102
|
-
const requestId = randomUUID();
|
|
103
|
-
const consentRequest: ConsentRequestRecord = {
|
|
104
|
-
requesterWallet,
|
|
105
|
-
targetWallet,
|
|
106
|
-
targetScopes: options.scopes,
|
|
107
|
-
purpose: options.purpose,
|
|
108
|
-
expiresAt: options.expiresIn ? now + options.expiresIn : undefined,
|
|
109
|
-
requestId,
|
|
110
|
-
createdAt: now,
|
|
111
|
-
updatedAt: now,
|
|
112
|
-
status: 'pending',
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
await this.persistConsentRequest(consentRequest);
|
|
116
|
-
|
|
117
|
-
// TODO: Send consent request to backend for UI presentation
|
|
118
|
-
// For now, return the persisted record so callers can track status locally
|
|
119
|
-
return consentRequest;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Grant permissions to an app (user approval)
|
|
124
|
-
* @param userAddress - User granting permissions
|
|
125
|
-
* @param options - Grant options
|
|
126
|
-
* @returns Created access grant
|
|
127
|
-
*/
|
|
128
|
-
async grantPermissions(
|
|
129
|
-
userAddress: string,
|
|
130
|
-
options: GrantPermissionsOptions & { signer?: Signer; appId?: string }
|
|
131
|
-
): Promise<AccessGrant> {
|
|
132
|
-
const requestingWallet = normalizeSuiAddress(options.requestingWallet);
|
|
133
|
-
const targetWallet = normalizeSuiAddress(options.targetWallet);
|
|
134
|
-
|
|
135
|
-
// Validate ownership - prefer capability-based validation
|
|
136
|
-
const hasAccess = await this.validateOwnership(userAddress, targetWallet, options.appId);
|
|
137
|
-
if (!hasAccess) {
|
|
138
|
-
throw new Error('User does not own the specified target wallet or capability');
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
let lastDigest: string | undefined;
|
|
142
|
-
if (options.signer) {
|
|
143
|
-
for (const scope of options.scopes) {
|
|
144
|
-
const accessLevel = scope.startsWith('write:') ? 'write' : 'read';
|
|
145
|
-
lastDigest = await this.crossContextPermissions.grantWalletAllowlistAccess(
|
|
146
|
-
{
|
|
147
|
-
requestingWallet,
|
|
148
|
-
targetWallet,
|
|
149
|
-
scope,
|
|
150
|
-
accessLevel,
|
|
151
|
-
expiresAt: options.expiresAt ?? 0,
|
|
152
|
-
},
|
|
153
|
-
options.signer,
|
|
154
|
-
);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
const permissions = await this.crossContextPermissions.queryWalletPermissions({
|
|
159
|
-
requestingWallet,
|
|
160
|
-
targetWallet,
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
const grantFromChain = this.buildGrantFromPermissions(
|
|
164
|
-
requestingWallet,
|
|
165
|
-
targetWallet,
|
|
166
|
-
permissions,
|
|
167
|
-
);
|
|
168
|
-
|
|
169
|
-
const now = Date.now();
|
|
170
|
-
let grant: AccessGrant =
|
|
171
|
-
grantFromChain ?? {
|
|
172
|
-
id: `grant_${requestingWallet}_${targetWallet}_${now}`,
|
|
173
|
-
requestingWallet,
|
|
174
|
-
targetWallet,
|
|
175
|
-
scopes: options.scopes,
|
|
176
|
-
expiresAt: options.expiresAt,
|
|
177
|
-
grantedAt: now,
|
|
178
|
-
};
|
|
179
|
-
|
|
180
|
-
if (lastDigest) {
|
|
181
|
-
grant = {
|
|
182
|
-
...grant,
|
|
183
|
-
transactionDigest: lastDigest,
|
|
184
|
-
};
|
|
185
|
-
}
|
|
186
|
-
await this.updateConsentStatus({
|
|
187
|
-
requesterWallet: requestingWallet,
|
|
188
|
-
targetWallet,
|
|
189
|
-
newStatus: 'approved',
|
|
190
|
-
updatedAt: now,
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
return grant;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
/**
|
|
197
|
-
* Revoke permissions from an app
|
|
198
|
-
* @param userAddress - User revoking permissions
|
|
199
|
-
* @param options - Revoke options
|
|
200
|
-
* @returns Success status
|
|
201
|
-
*/
|
|
202
|
-
async revokePermissions(
|
|
203
|
-
userAddress: string,
|
|
204
|
-
options: RevokePermissionsOptions & { signer?: Signer; appId?: string }
|
|
205
|
-
): Promise<boolean> {
|
|
206
|
-
const requestingWallet = normalizeSuiAddress(options.requestingWallet);
|
|
207
|
-
const targetWallet = normalizeSuiAddress(options.targetWallet);
|
|
208
|
-
|
|
209
|
-
// Validate ownership - prefer capability-based validation
|
|
210
|
-
const hasAccess = await this.validateOwnership(userAddress, targetWallet, options.appId);
|
|
211
|
-
if (!hasAccess) {
|
|
212
|
-
throw new Error('User does not own the specified target wallet or capability');
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
if (options.signer) {
|
|
216
|
-
await this.crossContextPermissions.revokeWalletAllowlistAccess(
|
|
217
|
-
{
|
|
218
|
-
requestingWallet,
|
|
219
|
-
targetWallet,
|
|
220
|
-
scope: options.scope,
|
|
221
|
-
},
|
|
222
|
-
options.signer,
|
|
223
|
-
);
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
return true;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
/**
|
|
230
|
-
* Determine if a requesting wallet currently has permission to access a target wallet
|
|
231
|
-
*/
|
|
232
|
-
async hasWalletPermission(
|
|
233
|
-
requestingWallet: string,
|
|
234
|
-
targetWallet: string,
|
|
235
|
-
scope: PermissionScope
|
|
236
|
-
): Promise<boolean> {
|
|
237
|
-
return await this.crossContextPermissions.hasWalletPermission({
|
|
238
|
-
requestingWallet: normalizeSuiAddress(requestingWallet),
|
|
239
|
-
targetWallet: normalizeSuiAddress(targetWallet),
|
|
240
|
-
scope,
|
|
241
|
-
});
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
/**
|
|
245
|
-
* Legacy compatibility method for app-scoped permission checks
|
|
246
|
-
* Interprets appId as requesting wallet address.
|
|
247
|
-
*/
|
|
248
|
-
async checkPermission(
|
|
249
|
-
appId: string,
|
|
250
|
-
scope: PermissionScope,
|
|
251
|
-
userAddressOrTargetWallet: string
|
|
252
|
-
): Promise<boolean> {
|
|
253
|
-
return await this.hasWalletPermission(appId, userAddressOrTargetWallet, scope);
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
/**
|
|
257
|
-
* Get all access grants by a user
|
|
258
|
-
* @param userAddress - User address
|
|
259
|
-
* @returns Array of access grants
|
|
260
|
-
*/
|
|
261
|
-
async getGrantsByUser(userAddress: string): Promise<AccessGrant[]> {
|
|
262
|
-
const normalized = normalizeSuiAddress(userAddress);
|
|
263
|
-
const permissions = await this.crossContextPermissions.listGrantsByTarget(normalized);
|
|
264
|
-
return this.convertPermissionsToGrants(permissions);
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
/**
|
|
268
|
-
* List all consent requests for a user
|
|
269
|
-
* @param userAddress - User address
|
|
270
|
-
* @returns Array of pending consent requests
|
|
271
|
-
*/
|
|
272
|
-
async getPendingConsents(userAddress: string): Promise<ConsentRequest[]> {
|
|
273
|
-
const normalized = normalizeSuiAddress(userAddress);
|
|
274
|
-
if (this.consentRepository) {
|
|
275
|
-
return await this.consentRepository.listByTarget(normalized, 'pending');
|
|
276
|
-
}
|
|
277
|
-
return Array.from(this.pendingConsents.values())
|
|
278
|
-
.filter((request) => request.targetWallet === normalized && request.status === 'pending')
|
|
279
|
-
.map((request) => ({ ...request }));
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
/**
|
|
283
|
-
* Approve a consent request
|
|
284
|
-
* @param userAddress - User approving the request
|
|
285
|
-
* @param consentRequest - Consent request to approve
|
|
286
|
-
* @param contextId - Context ID to grant access to
|
|
287
|
-
* @returns Created access grant
|
|
288
|
-
*/
|
|
289
|
-
async approveConsent(
|
|
290
|
-
userAddress: string,
|
|
291
|
-
consentRequest: ConsentRequest,
|
|
292
|
-
_contextId: string
|
|
293
|
-
): Promise<AccessGrant> {
|
|
294
|
-
const grant = await this.grantPermissions(userAddress, {
|
|
295
|
-
requestingWallet: consentRequest.requesterWallet,
|
|
296
|
-
targetWallet: consentRequest.targetWallet,
|
|
297
|
-
scopes: consentRequest.targetScopes,
|
|
298
|
-
expiresAt: consentRequest.expiresAt
|
|
299
|
-
});
|
|
300
|
-
|
|
301
|
-
const updatedAt = Date.now();
|
|
302
|
-
if (consentRequest.requestId) {
|
|
303
|
-
await this.updateConsentStatus({
|
|
304
|
-
requesterWallet: consentRequest.requesterWallet,
|
|
305
|
-
targetWallet: consentRequest.targetWallet,
|
|
306
|
-
newStatus: 'approved',
|
|
307
|
-
updatedAt,
|
|
308
|
-
requestId: consentRequest.requestId,
|
|
309
|
-
});
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
return grant;
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
/**
|
|
316
|
-
* Deny a consent request
|
|
317
|
-
* @param userAddress - User denying the request
|
|
318
|
-
* @param consentRequest - Consent request to deny
|
|
319
|
-
* @returns Success status
|
|
320
|
-
*/
|
|
321
|
-
async denyConsent(userAddress: string, consentRequest: ConsentRequest): Promise<boolean> {
|
|
322
|
-
const now = Date.now();
|
|
323
|
-
if (consentRequest.requestId) {
|
|
324
|
-
await this.updateConsentStatus({
|
|
325
|
-
requesterWallet: consentRequest.requesterWallet,
|
|
326
|
-
targetWallet: consentRequest.targetWallet,
|
|
327
|
-
newStatus: 'denied',
|
|
328
|
-
updatedAt: now,
|
|
329
|
-
requestId: consentRequest.requestId,
|
|
330
|
-
});
|
|
331
|
-
} else {
|
|
332
|
-
const inferredRequestId = randomUUID();
|
|
333
|
-
await this.persistConsentRequest({
|
|
334
|
-
...consentRequest,
|
|
335
|
-
requestId: inferredRequestId,
|
|
336
|
-
createdAt: now,
|
|
337
|
-
updatedAt: now,
|
|
338
|
-
status: 'denied',
|
|
339
|
-
});
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
return true;
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
/**
|
|
346
|
-
* Get permission audit log for a user
|
|
347
|
-
* @param userAddress - User address
|
|
348
|
-
* @returns Array of permission events
|
|
349
|
-
*/
|
|
350
|
-
async getPermissionAudit(userAddress: string): Promise<Array<{
|
|
351
|
-
timestamp: number;
|
|
352
|
-
action: 'grant' | 'revoke' | 'request' | 'deny';
|
|
353
|
-
requestingWallet: string;
|
|
354
|
-
targetWallet: string;
|
|
355
|
-
scopes: PermissionScope[];
|
|
356
|
-
}>> {
|
|
357
|
-
const normalized = normalizeSuiAddress(userAddress);
|
|
358
|
-
const auditEntries: Array<{
|
|
359
|
-
timestamp: number;
|
|
360
|
-
action: 'grant' | 'revoke' | 'request' | 'deny';
|
|
361
|
-
requestingWallet: string;
|
|
362
|
-
targetWallet: string;
|
|
363
|
-
scopes: PermissionScope[];
|
|
364
|
-
}> = [];
|
|
365
|
-
|
|
366
|
-
const consentRecords = this.consentRepository
|
|
367
|
-
? await this.consentRepository.listByTarget(normalized)
|
|
368
|
-
: Array.from(this.pendingConsents.values()).filter((request) => request.targetWallet === normalized);
|
|
369
|
-
|
|
370
|
-
for (const request of consentRecords) {
|
|
371
|
-
auditEntries.push({
|
|
372
|
-
timestamp: request.createdAt,
|
|
373
|
-
action: 'request',
|
|
374
|
-
requestingWallet: request.requesterWallet,
|
|
375
|
-
targetWallet: request.targetWallet,
|
|
376
|
-
scopes: request.targetScopes,
|
|
377
|
-
});
|
|
378
|
-
|
|
379
|
-
if (request.status === 'denied') {
|
|
380
|
-
auditEntries.push({
|
|
381
|
-
timestamp: request.updatedAt,
|
|
382
|
-
action: 'deny',
|
|
383
|
-
requestingWallet: request.requesterWallet,
|
|
384
|
-
targetWallet: request.targetWallet,
|
|
385
|
-
scopes: request.targetScopes,
|
|
386
|
-
});
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
const history = await this.crossContextPermissions.getWalletAllowlistHistory({
|
|
391
|
-
targetWallet: normalized,
|
|
392
|
-
});
|
|
393
|
-
|
|
394
|
-
for (const event of history) {
|
|
395
|
-
auditEntries.push({
|
|
396
|
-
timestamp: event.timestamp,
|
|
397
|
-
action: event.action,
|
|
398
|
-
requestingWallet: event.requestingWallet,
|
|
399
|
-
targetWallet: event.targetWallet,
|
|
400
|
-
scopes: [this.toPermissionScope(event.scope)],
|
|
401
|
-
});
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
return auditEntries.sort((a, b) => a.timestamp - b.timestamp);
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
/**
|
|
408
|
-
* Validate OAuth-style permission for SEAL access control
|
|
409
|
-
* @param walletOwner - Owner of the wallet
|
|
410
|
-
* @param appId - Application requesting access
|
|
411
|
-
* @param requestedScope - Required permission scope
|
|
412
|
-
* @returns True if permission is valid
|
|
413
|
-
*/
|
|
414
|
-
async validateOAuthPermission(
|
|
415
|
-
walletOwner: string,
|
|
416
|
-
appId: string,
|
|
417
|
-
requestedScope: string
|
|
418
|
-
): Promise<boolean> {
|
|
419
|
-
// This integrates with our existing SEAL access control
|
|
420
|
-
return await this.hasWalletPermission(appId, walletOwner, requestedScope as PermissionScope);
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
/**
|
|
424
|
-
* Build seal_approve transaction for a requesting wallet
|
|
425
|
-
*/
|
|
426
|
-
createApprovalTransaction(
|
|
427
|
-
contentId: Uint8Array,
|
|
428
|
-
requestingWallet: string
|
|
429
|
-
): Transaction {
|
|
430
|
-
return this.crossContextPermissions.buildSealApproveTransaction(
|
|
431
|
-
contentId,
|
|
432
|
-
normalizeSuiAddress(requestingWallet),
|
|
433
|
-
);
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
/**
|
|
437
|
-
* Get permission statistics for a user
|
|
438
|
-
* @param userAddress - User address
|
|
439
|
-
* @returns Permission usage statistics
|
|
440
|
-
*/
|
|
441
|
-
async getPermissionStats(userAddress: string): Promise<{
|
|
442
|
-
totalGrants: number;
|
|
443
|
-
activeGrants: number;
|
|
444
|
-
totalApps: number;
|
|
445
|
-
totalScopes: number;
|
|
446
|
-
recentActivity: number;
|
|
447
|
-
}> {
|
|
448
|
-
const grants = await this.getGrantsByUser(userAddress);
|
|
449
|
-
const now = Date.now();
|
|
450
|
-
|
|
451
|
-
const activeGrants = grants.filter((g) => !g.expiresAt || g.expiresAt > now);
|
|
452
|
-
const uniqueApps = new Set(grants.map((g) => g.requestingWallet));
|
|
453
|
-
const uniqueScopes = new Set(grants.flatMap((g) => g.scopes));
|
|
454
|
-
|
|
455
|
-
const recentActivity =
|
|
456
|
-
grants.length > 0 ? Math.max(...grants.map((g) => g.expiresAt || 0)) : 0;
|
|
457
|
-
|
|
458
|
-
return {
|
|
459
|
-
totalGrants: grants.length,
|
|
460
|
-
activeGrants: activeGrants.length,
|
|
461
|
-
totalApps: uniqueApps.size,
|
|
462
|
-
totalScopes: uniqueScopes.size,
|
|
463
|
-
recentActivity
|
|
464
|
-
};
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
/**
|
|
468
|
-
* Clean up expired permissions
|
|
469
|
-
* @param userAddress - User address
|
|
470
|
-
* @returns Number of permissions cleaned up
|
|
471
|
-
*/
|
|
472
|
-
async cleanupExpiredPermissions(userAddress: string): Promise<number> {
|
|
473
|
-
const now = Date.now();
|
|
474
|
-
const grants = await this.getGrantsByUser(userAddress);
|
|
475
|
-
|
|
476
|
-
return grants.filter((grant) => {
|
|
477
|
-
if (!grant.expiresAt || grant.expiresAt === 0) {
|
|
478
|
-
return false;
|
|
479
|
-
}
|
|
480
|
-
return grant.expiresAt <= now;
|
|
481
|
-
}).length;
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
private buildGrantFromPermissions(
|
|
485
|
-
requestingWallet: string,
|
|
486
|
-
targetWallet: string,
|
|
487
|
-
permissions: WalletAllowlistPermission[],
|
|
488
|
-
): AccessGrant | undefined {
|
|
489
|
-
const grants = this.convertPermissionsToGrants(permissions);
|
|
490
|
-
return grants.find(
|
|
491
|
-
(candidate) =>
|
|
492
|
-
candidate.requestingWallet === requestingWallet &&
|
|
493
|
-
candidate.targetWallet === targetWallet,
|
|
494
|
-
);
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
private convertPermissionsToGrants(
|
|
498
|
-
permissions: WalletAllowlistPermission[],
|
|
499
|
-
): AccessGrant[] {
|
|
500
|
-
const grouped = new Map<string, WalletAllowlistPermission[]>();
|
|
501
|
-
|
|
502
|
-
for (const permission of permissions) {
|
|
503
|
-
const key = `${permission.requestingWallet}-${permission.targetWallet}`;
|
|
504
|
-
const existing = grouped.get(key);
|
|
505
|
-
if (existing) {
|
|
506
|
-
existing.push(permission);
|
|
507
|
-
} else {
|
|
508
|
-
grouped.set(key, [permission]);
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
const grants: AccessGrant[] = [];
|
|
513
|
-
for (const entries of grouped.values()) {
|
|
514
|
-
if (entries.length === 0) {
|
|
515
|
-
continue;
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
const [first] = entries;
|
|
519
|
-
const scopes = new Set<PermissionScope>();
|
|
520
|
-
let earliestGrantedAt = entries[0].grantedAt;
|
|
521
|
-
let expiresAtCandidate = 0;
|
|
522
|
-
|
|
523
|
-
for (const entry of entries) {
|
|
524
|
-
scopes.add(this.toPermissionScope(entry.scope));
|
|
525
|
-
if (entry.grantedAt < earliestGrantedAt) {
|
|
526
|
-
earliestGrantedAt = entry.grantedAt;
|
|
527
|
-
}
|
|
528
|
-
if (entry.expiresAt > expiresAtCandidate) {
|
|
529
|
-
expiresAtCandidate = entry.expiresAt;
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
const grant: AccessGrant = {
|
|
534
|
-
id: `grant_${first.requestingWallet}_${first.targetWallet}_${earliestGrantedAt}`,
|
|
535
|
-
requestingWallet: first.requestingWallet,
|
|
536
|
-
targetWallet: first.targetWallet,
|
|
537
|
-
scopes: Array.from(scopes),
|
|
538
|
-
grantedAt: earliestGrantedAt,
|
|
539
|
-
};
|
|
540
|
-
|
|
541
|
-
if (expiresAtCandidate > 0) {
|
|
542
|
-
grant.expiresAt = expiresAtCandidate;
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
grants.push(grant);
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
return grants;
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
private toPermissionScope(scope: string): PermissionScope {
|
|
552
|
-
return scope as PermissionScope;
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
private async persistConsentRequest(record: ConsentRequestRecord): Promise<void> {
|
|
556
|
-
if (this.consentRepository) {
|
|
557
|
-
await this.consentRepository.save(record);
|
|
558
|
-
} else {
|
|
559
|
-
this.pendingConsents.set(record.requestId, { ...record });
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
/**
|
|
564
|
-
* Swap the consent persistence backend at runtime.
|
|
565
|
-
* Useful for applications that want to wire a custom repository after
|
|
566
|
-
* the service has been constructed (e.g., demos supplying a filesystem store).
|
|
567
|
-
*/
|
|
568
|
-
setConsentRepository(repository?: ConsentRepository): void {
|
|
569
|
-
this.consentRepository = repository;
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
/**
|
|
573
|
-
* Validate user ownership of a target wallet or capability
|
|
574
|
-
* Prefers capability-based validation over legacy ContextWalletService
|
|
575
|
-
*
|
|
576
|
-
* @param userAddress - User's address
|
|
577
|
-
* @param targetWallet - Target wallet address
|
|
578
|
-
* @param appId - Optional app ID for capability lookup
|
|
579
|
-
* @returns True if user has ownership/access
|
|
580
|
-
*/
|
|
581
|
-
private async validateOwnership(
|
|
582
|
-
userAddress: string,
|
|
583
|
-
targetWallet: string,
|
|
584
|
-
appId?: string
|
|
585
|
-
): Promise<boolean> {
|
|
586
|
-
// Prefer capability-based validation (new architecture)
|
|
587
|
-
if (this.capabilityService && appId) {
|
|
588
|
-
return await this.capabilityService.hasCapability(userAddress, appId);
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
// Fall back to legacy ContextWalletService validation
|
|
592
|
-
if (this.contextWalletService) {
|
|
593
|
-
return await this.contextWalletService.validateAccess(targetWallet, userAddress);
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
// If neither service is available, skip validation (trust caller)
|
|
597
|
-
return true;
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
private async updateConsentStatus(params: {
|
|
601
|
-
requesterWallet: string;
|
|
602
|
-
targetWallet: string;
|
|
603
|
-
newStatus: ConsentStatus;
|
|
604
|
-
updatedAt: number;
|
|
605
|
-
requestId?: string;
|
|
606
|
-
}): Promise<void> {
|
|
607
|
-
const normalizedRequester = normalizeSuiAddress(params.requesterWallet);
|
|
608
|
-
const normalizedTarget = normalizeSuiAddress(params.targetWallet);
|
|
609
|
-
|
|
610
|
-
if (this.consentRepository) {
|
|
611
|
-
if (params.requestId) {
|
|
612
|
-
await this.consentRepository.updateStatus(params.requestId, params.newStatus, params.updatedAt);
|
|
613
|
-
} else {
|
|
614
|
-
const pendingRecords = await this.consentRepository.listByTarget(normalizedTarget, 'pending');
|
|
615
|
-
await Promise.all(
|
|
616
|
-
pendingRecords
|
|
617
|
-
.filter((record) => record.requesterWallet === normalizedRequester)
|
|
618
|
-
.map((record) =>
|
|
619
|
-
this.consentRepository!.updateStatus(record.requestId, params.newStatus, params.updatedAt),
|
|
620
|
-
),
|
|
621
|
-
);
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
for (const [requestId, record] of this.pendingConsents.entries()) {
|
|
626
|
-
if (record.requesterWallet === normalizedRequester && record.targetWallet === normalizedTarget) {
|
|
627
|
-
const updatedRecord: ConsentRequestRecord = {
|
|
628
|
-
...record,
|
|
629
|
-
status: params.newStatus,
|
|
630
|
-
updatedAt: params.updatedAt,
|
|
631
|
-
};
|
|
632
|
-
this.pendingConsents.set(requestId, updatedRecord);
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* PermissionService - OAuth-style access control management
|
|
3
|
+
*
|
|
4
|
+
* Manages permissions for cross-app data access, including:
|
|
5
|
+
* - OAuth-style consent requests and grants
|
|
6
|
+
* - Permission validation and enforcement
|
|
7
|
+
* - On-chain access control integration
|
|
8
|
+
* - Permission auditing and revocation
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// Use Web Crypto API (browser-compatible)
|
|
12
|
+
const randomUUID = (): string => crypto.randomUUID();
|
|
13
|
+
import { SuiClient } from '@mysten/sui/client';
|
|
14
|
+
import { Transaction } from '@mysten/sui/transactions';
|
|
15
|
+
import { normalizeSuiAddress } from '@mysten/sui/utils';
|
|
16
|
+
import type { Signer } from '@mysten/sui/cryptography';
|
|
17
|
+
|
|
18
|
+
import {
|
|
19
|
+
ConsentRequest,
|
|
20
|
+
ConsentRequestRecord,
|
|
21
|
+
ConsentStatus,
|
|
22
|
+
AccessGrant,
|
|
23
|
+
PermissionScope,
|
|
24
|
+
RequestConsentOptions,
|
|
25
|
+
GrantPermissionsOptions,
|
|
26
|
+
RevokePermissionsOptions
|
|
27
|
+
} from '../core/types/wallet.js';
|
|
28
|
+
import { ContextWalletService } from '../wallet/ContextWalletService.js';
|
|
29
|
+
import { CapabilityService } from '../services/CapabilityService.js';
|
|
30
|
+
import { CrossContextPermissionService } from '../services/CrossContextPermissionService';
|
|
31
|
+
import type { WalletAllowlistPermission } from '../services/CrossContextPermissionService';
|
|
32
|
+
import type { ConsentRepository } from '../permissions/ConsentRepository.js';
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Configuration for PermissionService
|
|
36
|
+
*/
|
|
37
|
+
export interface PermissionServiceConfig {
|
|
38
|
+
/** Sui client instance */
|
|
39
|
+
suiClient: SuiClient;
|
|
40
|
+
/** Package ID for Move contracts */
|
|
41
|
+
packageId: string;
|
|
42
|
+
/** Access registry ID for wallet allowlists */
|
|
43
|
+
accessRegistryId: string;
|
|
44
|
+
/**
|
|
45
|
+
* @deprecated Use capabilityService instead for capability-based architecture
|
|
46
|
+
* ContextWalletService for validation (legacy)
|
|
47
|
+
*/
|
|
48
|
+
contextWalletService?: ContextWalletService;
|
|
49
|
+
/** CapabilityService for capability-based validation (preferred) */
|
|
50
|
+
capabilityService?: CapabilityService;
|
|
51
|
+
/** Optional injected cross-context permission service */
|
|
52
|
+
crossContextPermissionService?: CrossContextPermissionService;
|
|
53
|
+
/** Optional repository for consent persistence */
|
|
54
|
+
consentRepository?: ConsentRepository;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* PermissionService handles OAuth-style access control
|
|
59
|
+
*/
|
|
60
|
+
export class PermissionService {
|
|
61
|
+
private suiClient: SuiClient;
|
|
62
|
+
private packageId: string;
|
|
63
|
+
/** @deprecated Use capabilityService instead */
|
|
64
|
+
private contextWalletService?: ContextWalletService;
|
|
65
|
+
private capabilityService?: CapabilityService;
|
|
66
|
+
private crossContextPermissions: CrossContextPermissionService;
|
|
67
|
+
private pendingConsents: Map<string, ConsentRequestRecord> = new Map();
|
|
68
|
+
private consentRepository?: ConsentRepository;
|
|
69
|
+
|
|
70
|
+
constructor(config: PermissionServiceConfig) {
|
|
71
|
+
this.suiClient = config.suiClient;
|
|
72
|
+
this.packageId = config.packageId;
|
|
73
|
+
this.contextWalletService = config.contextWalletService;
|
|
74
|
+
this.capabilityService = config.capabilityService;
|
|
75
|
+
this.consentRepository = config.consentRepository;
|
|
76
|
+
|
|
77
|
+
if (!config.accessRegistryId) {
|
|
78
|
+
throw new Error('PermissionService requires accessRegistryId for wallet-based permissions');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
this.crossContextPermissions =
|
|
82
|
+
config.crossContextPermissionService ??
|
|
83
|
+
new CrossContextPermissionService(
|
|
84
|
+
{
|
|
85
|
+
packageId: this.packageId,
|
|
86
|
+
accessRegistryId: config.accessRegistryId,
|
|
87
|
+
},
|
|
88
|
+
this.suiClient,
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Request user consent for accessing data
|
|
94
|
+
* @param options - Consent request options
|
|
95
|
+
* @returns Created consent request
|
|
96
|
+
*/
|
|
97
|
+
async requestConsent(options: RequestConsentOptions): Promise<ConsentRequestRecord> {
|
|
98
|
+
const requesterWallet = normalizeSuiAddress(options.requesterWallet);
|
|
99
|
+
const targetWallet = normalizeSuiAddress(options.targetWallet);
|
|
100
|
+
const now = Date.now();
|
|
101
|
+
|
|
102
|
+
const requestId = randomUUID();
|
|
103
|
+
const consentRequest: ConsentRequestRecord = {
|
|
104
|
+
requesterWallet,
|
|
105
|
+
targetWallet,
|
|
106
|
+
targetScopes: options.scopes,
|
|
107
|
+
purpose: options.purpose,
|
|
108
|
+
expiresAt: options.expiresIn ? now + options.expiresIn : undefined,
|
|
109
|
+
requestId,
|
|
110
|
+
createdAt: now,
|
|
111
|
+
updatedAt: now,
|
|
112
|
+
status: 'pending',
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
await this.persistConsentRequest(consentRequest);
|
|
116
|
+
|
|
117
|
+
// TODO: Send consent request to backend for UI presentation
|
|
118
|
+
// For now, return the persisted record so callers can track status locally
|
|
119
|
+
return consentRequest;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Grant permissions to an app (user approval)
|
|
124
|
+
* @param userAddress - User granting permissions
|
|
125
|
+
* @param options - Grant options
|
|
126
|
+
* @returns Created access grant
|
|
127
|
+
*/
|
|
128
|
+
async grantPermissions(
|
|
129
|
+
userAddress: string,
|
|
130
|
+
options: GrantPermissionsOptions & { signer?: Signer; appId?: string }
|
|
131
|
+
): Promise<AccessGrant> {
|
|
132
|
+
const requestingWallet = normalizeSuiAddress(options.requestingWallet);
|
|
133
|
+
const targetWallet = normalizeSuiAddress(options.targetWallet);
|
|
134
|
+
|
|
135
|
+
// Validate ownership - prefer capability-based validation
|
|
136
|
+
const hasAccess = await this.validateOwnership(userAddress, targetWallet, options.appId);
|
|
137
|
+
if (!hasAccess) {
|
|
138
|
+
throw new Error('User does not own the specified target wallet or capability');
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
let lastDigest: string | undefined;
|
|
142
|
+
if (options.signer) {
|
|
143
|
+
for (const scope of options.scopes) {
|
|
144
|
+
const accessLevel = scope.startsWith('write:') ? 'write' : 'read';
|
|
145
|
+
lastDigest = await this.crossContextPermissions.grantWalletAllowlistAccess(
|
|
146
|
+
{
|
|
147
|
+
requestingWallet,
|
|
148
|
+
targetWallet,
|
|
149
|
+
scope,
|
|
150
|
+
accessLevel,
|
|
151
|
+
expiresAt: options.expiresAt ?? 0,
|
|
152
|
+
},
|
|
153
|
+
options.signer,
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const permissions = await this.crossContextPermissions.queryWalletPermissions({
|
|
159
|
+
requestingWallet,
|
|
160
|
+
targetWallet,
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
const grantFromChain = this.buildGrantFromPermissions(
|
|
164
|
+
requestingWallet,
|
|
165
|
+
targetWallet,
|
|
166
|
+
permissions,
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
const now = Date.now();
|
|
170
|
+
let grant: AccessGrant =
|
|
171
|
+
grantFromChain ?? {
|
|
172
|
+
id: `grant_${requestingWallet}_${targetWallet}_${now}`,
|
|
173
|
+
requestingWallet,
|
|
174
|
+
targetWallet,
|
|
175
|
+
scopes: options.scopes,
|
|
176
|
+
expiresAt: options.expiresAt,
|
|
177
|
+
grantedAt: now,
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
if (lastDigest) {
|
|
181
|
+
grant = {
|
|
182
|
+
...grant,
|
|
183
|
+
transactionDigest: lastDigest,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
await this.updateConsentStatus({
|
|
187
|
+
requesterWallet: requestingWallet,
|
|
188
|
+
targetWallet,
|
|
189
|
+
newStatus: 'approved',
|
|
190
|
+
updatedAt: now,
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
return grant;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Revoke permissions from an app
|
|
198
|
+
* @param userAddress - User revoking permissions
|
|
199
|
+
* @param options - Revoke options
|
|
200
|
+
* @returns Success status
|
|
201
|
+
*/
|
|
202
|
+
async revokePermissions(
|
|
203
|
+
userAddress: string,
|
|
204
|
+
options: RevokePermissionsOptions & { signer?: Signer; appId?: string }
|
|
205
|
+
): Promise<boolean> {
|
|
206
|
+
const requestingWallet = normalizeSuiAddress(options.requestingWallet);
|
|
207
|
+
const targetWallet = normalizeSuiAddress(options.targetWallet);
|
|
208
|
+
|
|
209
|
+
// Validate ownership - prefer capability-based validation
|
|
210
|
+
const hasAccess = await this.validateOwnership(userAddress, targetWallet, options.appId);
|
|
211
|
+
if (!hasAccess) {
|
|
212
|
+
throw new Error('User does not own the specified target wallet or capability');
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (options.signer) {
|
|
216
|
+
await this.crossContextPermissions.revokeWalletAllowlistAccess(
|
|
217
|
+
{
|
|
218
|
+
requestingWallet,
|
|
219
|
+
targetWallet,
|
|
220
|
+
scope: options.scope,
|
|
221
|
+
},
|
|
222
|
+
options.signer,
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return true;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Determine if a requesting wallet currently has permission to access a target wallet
|
|
231
|
+
*/
|
|
232
|
+
async hasWalletPermission(
|
|
233
|
+
requestingWallet: string,
|
|
234
|
+
targetWallet: string,
|
|
235
|
+
scope: PermissionScope
|
|
236
|
+
): Promise<boolean> {
|
|
237
|
+
return await this.crossContextPermissions.hasWalletPermission({
|
|
238
|
+
requestingWallet: normalizeSuiAddress(requestingWallet),
|
|
239
|
+
targetWallet: normalizeSuiAddress(targetWallet),
|
|
240
|
+
scope,
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Legacy compatibility method for app-scoped permission checks
|
|
246
|
+
* Interprets appId as requesting wallet address.
|
|
247
|
+
*/
|
|
248
|
+
async checkPermission(
|
|
249
|
+
appId: string,
|
|
250
|
+
scope: PermissionScope,
|
|
251
|
+
userAddressOrTargetWallet: string
|
|
252
|
+
): Promise<boolean> {
|
|
253
|
+
return await this.hasWalletPermission(appId, userAddressOrTargetWallet, scope);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Get all access grants by a user
|
|
258
|
+
* @param userAddress - User address
|
|
259
|
+
* @returns Array of access grants
|
|
260
|
+
*/
|
|
261
|
+
async getGrantsByUser(userAddress: string): Promise<AccessGrant[]> {
|
|
262
|
+
const normalized = normalizeSuiAddress(userAddress);
|
|
263
|
+
const permissions = await this.crossContextPermissions.listGrantsByTarget(normalized);
|
|
264
|
+
return this.convertPermissionsToGrants(permissions);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* List all consent requests for a user
|
|
269
|
+
* @param userAddress - User address
|
|
270
|
+
* @returns Array of pending consent requests
|
|
271
|
+
*/
|
|
272
|
+
async getPendingConsents(userAddress: string): Promise<ConsentRequest[]> {
|
|
273
|
+
const normalized = normalizeSuiAddress(userAddress);
|
|
274
|
+
if (this.consentRepository) {
|
|
275
|
+
return await this.consentRepository.listByTarget(normalized, 'pending');
|
|
276
|
+
}
|
|
277
|
+
return Array.from(this.pendingConsents.values())
|
|
278
|
+
.filter((request) => request.targetWallet === normalized && request.status === 'pending')
|
|
279
|
+
.map((request) => ({ ...request }));
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Approve a consent request
|
|
284
|
+
* @param userAddress - User approving the request
|
|
285
|
+
* @param consentRequest - Consent request to approve
|
|
286
|
+
* @param contextId - Context ID to grant access to
|
|
287
|
+
* @returns Created access grant
|
|
288
|
+
*/
|
|
289
|
+
async approveConsent(
|
|
290
|
+
userAddress: string,
|
|
291
|
+
consentRequest: ConsentRequest,
|
|
292
|
+
_contextId: string
|
|
293
|
+
): Promise<AccessGrant> {
|
|
294
|
+
const grant = await this.grantPermissions(userAddress, {
|
|
295
|
+
requestingWallet: consentRequest.requesterWallet,
|
|
296
|
+
targetWallet: consentRequest.targetWallet,
|
|
297
|
+
scopes: consentRequest.targetScopes,
|
|
298
|
+
expiresAt: consentRequest.expiresAt
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
const updatedAt = Date.now();
|
|
302
|
+
if (consentRequest.requestId) {
|
|
303
|
+
await this.updateConsentStatus({
|
|
304
|
+
requesterWallet: consentRequest.requesterWallet,
|
|
305
|
+
targetWallet: consentRequest.targetWallet,
|
|
306
|
+
newStatus: 'approved',
|
|
307
|
+
updatedAt,
|
|
308
|
+
requestId: consentRequest.requestId,
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return grant;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Deny a consent request
|
|
317
|
+
* @param userAddress - User denying the request
|
|
318
|
+
* @param consentRequest - Consent request to deny
|
|
319
|
+
* @returns Success status
|
|
320
|
+
*/
|
|
321
|
+
async denyConsent(userAddress: string, consentRequest: ConsentRequest): Promise<boolean> {
|
|
322
|
+
const now = Date.now();
|
|
323
|
+
if (consentRequest.requestId) {
|
|
324
|
+
await this.updateConsentStatus({
|
|
325
|
+
requesterWallet: consentRequest.requesterWallet,
|
|
326
|
+
targetWallet: consentRequest.targetWallet,
|
|
327
|
+
newStatus: 'denied',
|
|
328
|
+
updatedAt: now,
|
|
329
|
+
requestId: consentRequest.requestId,
|
|
330
|
+
});
|
|
331
|
+
} else {
|
|
332
|
+
const inferredRequestId = randomUUID();
|
|
333
|
+
await this.persistConsentRequest({
|
|
334
|
+
...consentRequest,
|
|
335
|
+
requestId: inferredRequestId,
|
|
336
|
+
createdAt: now,
|
|
337
|
+
updatedAt: now,
|
|
338
|
+
status: 'denied',
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
return true;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Get permission audit log for a user
|
|
347
|
+
* @param userAddress - User address
|
|
348
|
+
* @returns Array of permission events
|
|
349
|
+
*/
|
|
350
|
+
async getPermissionAudit(userAddress: string): Promise<Array<{
|
|
351
|
+
timestamp: number;
|
|
352
|
+
action: 'grant' | 'revoke' | 'request' | 'deny';
|
|
353
|
+
requestingWallet: string;
|
|
354
|
+
targetWallet: string;
|
|
355
|
+
scopes: PermissionScope[];
|
|
356
|
+
}>> {
|
|
357
|
+
const normalized = normalizeSuiAddress(userAddress);
|
|
358
|
+
const auditEntries: Array<{
|
|
359
|
+
timestamp: number;
|
|
360
|
+
action: 'grant' | 'revoke' | 'request' | 'deny';
|
|
361
|
+
requestingWallet: string;
|
|
362
|
+
targetWallet: string;
|
|
363
|
+
scopes: PermissionScope[];
|
|
364
|
+
}> = [];
|
|
365
|
+
|
|
366
|
+
const consentRecords = this.consentRepository
|
|
367
|
+
? await this.consentRepository.listByTarget(normalized)
|
|
368
|
+
: Array.from(this.pendingConsents.values()).filter((request) => request.targetWallet === normalized);
|
|
369
|
+
|
|
370
|
+
for (const request of consentRecords) {
|
|
371
|
+
auditEntries.push({
|
|
372
|
+
timestamp: request.createdAt,
|
|
373
|
+
action: 'request',
|
|
374
|
+
requestingWallet: request.requesterWallet,
|
|
375
|
+
targetWallet: request.targetWallet,
|
|
376
|
+
scopes: request.targetScopes,
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
if (request.status === 'denied') {
|
|
380
|
+
auditEntries.push({
|
|
381
|
+
timestamp: request.updatedAt,
|
|
382
|
+
action: 'deny',
|
|
383
|
+
requestingWallet: request.requesterWallet,
|
|
384
|
+
targetWallet: request.targetWallet,
|
|
385
|
+
scopes: request.targetScopes,
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
const history = await this.crossContextPermissions.getWalletAllowlistHistory({
|
|
391
|
+
targetWallet: normalized,
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
for (const event of history) {
|
|
395
|
+
auditEntries.push({
|
|
396
|
+
timestamp: event.timestamp,
|
|
397
|
+
action: event.action,
|
|
398
|
+
requestingWallet: event.requestingWallet,
|
|
399
|
+
targetWallet: event.targetWallet,
|
|
400
|
+
scopes: [this.toPermissionScope(event.scope)],
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
return auditEntries.sort((a, b) => a.timestamp - b.timestamp);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Validate OAuth-style permission for SEAL access control
|
|
409
|
+
* @param walletOwner - Owner of the wallet
|
|
410
|
+
* @param appId - Application requesting access
|
|
411
|
+
* @param requestedScope - Required permission scope
|
|
412
|
+
* @returns True if permission is valid
|
|
413
|
+
*/
|
|
414
|
+
async validateOAuthPermission(
|
|
415
|
+
walletOwner: string,
|
|
416
|
+
appId: string,
|
|
417
|
+
requestedScope: string
|
|
418
|
+
): Promise<boolean> {
|
|
419
|
+
// This integrates with our existing SEAL access control
|
|
420
|
+
return await this.hasWalletPermission(appId, walletOwner, requestedScope as PermissionScope);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Build seal_approve transaction for a requesting wallet
|
|
425
|
+
*/
|
|
426
|
+
createApprovalTransaction(
|
|
427
|
+
contentId: Uint8Array,
|
|
428
|
+
requestingWallet: string
|
|
429
|
+
): Transaction {
|
|
430
|
+
return this.crossContextPermissions.buildSealApproveTransaction(
|
|
431
|
+
contentId,
|
|
432
|
+
normalizeSuiAddress(requestingWallet),
|
|
433
|
+
);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Get permission statistics for a user
|
|
438
|
+
* @param userAddress - User address
|
|
439
|
+
* @returns Permission usage statistics
|
|
440
|
+
*/
|
|
441
|
+
async getPermissionStats(userAddress: string): Promise<{
|
|
442
|
+
totalGrants: number;
|
|
443
|
+
activeGrants: number;
|
|
444
|
+
totalApps: number;
|
|
445
|
+
totalScopes: number;
|
|
446
|
+
recentActivity: number;
|
|
447
|
+
}> {
|
|
448
|
+
const grants = await this.getGrantsByUser(userAddress);
|
|
449
|
+
const now = Date.now();
|
|
450
|
+
|
|
451
|
+
const activeGrants = grants.filter((g) => !g.expiresAt || g.expiresAt > now);
|
|
452
|
+
const uniqueApps = new Set(grants.map((g) => g.requestingWallet));
|
|
453
|
+
const uniqueScopes = new Set(grants.flatMap((g) => g.scopes));
|
|
454
|
+
|
|
455
|
+
const recentActivity =
|
|
456
|
+
grants.length > 0 ? Math.max(...grants.map((g) => g.expiresAt || 0)) : 0;
|
|
457
|
+
|
|
458
|
+
return {
|
|
459
|
+
totalGrants: grants.length,
|
|
460
|
+
activeGrants: activeGrants.length,
|
|
461
|
+
totalApps: uniqueApps.size,
|
|
462
|
+
totalScopes: uniqueScopes.size,
|
|
463
|
+
recentActivity
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Clean up expired permissions
|
|
469
|
+
* @param userAddress - User address
|
|
470
|
+
* @returns Number of permissions cleaned up
|
|
471
|
+
*/
|
|
472
|
+
async cleanupExpiredPermissions(userAddress: string): Promise<number> {
|
|
473
|
+
const now = Date.now();
|
|
474
|
+
const grants = await this.getGrantsByUser(userAddress);
|
|
475
|
+
|
|
476
|
+
return grants.filter((grant) => {
|
|
477
|
+
if (!grant.expiresAt || grant.expiresAt === 0) {
|
|
478
|
+
return false;
|
|
479
|
+
}
|
|
480
|
+
return grant.expiresAt <= now;
|
|
481
|
+
}).length;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
private buildGrantFromPermissions(
|
|
485
|
+
requestingWallet: string,
|
|
486
|
+
targetWallet: string,
|
|
487
|
+
permissions: WalletAllowlistPermission[],
|
|
488
|
+
): AccessGrant | undefined {
|
|
489
|
+
const grants = this.convertPermissionsToGrants(permissions);
|
|
490
|
+
return grants.find(
|
|
491
|
+
(candidate) =>
|
|
492
|
+
candidate.requestingWallet === requestingWallet &&
|
|
493
|
+
candidate.targetWallet === targetWallet,
|
|
494
|
+
);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
private convertPermissionsToGrants(
|
|
498
|
+
permissions: WalletAllowlistPermission[],
|
|
499
|
+
): AccessGrant[] {
|
|
500
|
+
const grouped = new Map<string, WalletAllowlistPermission[]>();
|
|
501
|
+
|
|
502
|
+
for (const permission of permissions) {
|
|
503
|
+
const key = `${permission.requestingWallet}-${permission.targetWallet}`;
|
|
504
|
+
const existing = grouped.get(key);
|
|
505
|
+
if (existing) {
|
|
506
|
+
existing.push(permission);
|
|
507
|
+
} else {
|
|
508
|
+
grouped.set(key, [permission]);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
const grants: AccessGrant[] = [];
|
|
513
|
+
for (const entries of grouped.values()) {
|
|
514
|
+
if (entries.length === 0) {
|
|
515
|
+
continue;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
const [first] = entries;
|
|
519
|
+
const scopes = new Set<PermissionScope>();
|
|
520
|
+
let earliestGrantedAt = entries[0].grantedAt;
|
|
521
|
+
let expiresAtCandidate = 0;
|
|
522
|
+
|
|
523
|
+
for (const entry of entries) {
|
|
524
|
+
scopes.add(this.toPermissionScope(entry.scope));
|
|
525
|
+
if (entry.grantedAt < earliestGrantedAt) {
|
|
526
|
+
earliestGrantedAt = entry.grantedAt;
|
|
527
|
+
}
|
|
528
|
+
if (entry.expiresAt > expiresAtCandidate) {
|
|
529
|
+
expiresAtCandidate = entry.expiresAt;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
const grant: AccessGrant = {
|
|
534
|
+
id: `grant_${first.requestingWallet}_${first.targetWallet}_${earliestGrantedAt}`,
|
|
535
|
+
requestingWallet: first.requestingWallet,
|
|
536
|
+
targetWallet: first.targetWallet,
|
|
537
|
+
scopes: Array.from(scopes),
|
|
538
|
+
grantedAt: earliestGrantedAt,
|
|
539
|
+
};
|
|
540
|
+
|
|
541
|
+
if (expiresAtCandidate > 0) {
|
|
542
|
+
grant.expiresAt = expiresAtCandidate;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
grants.push(grant);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
return grants;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
private toPermissionScope(scope: string): PermissionScope {
|
|
552
|
+
return scope as PermissionScope;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
private async persistConsentRequest(record: ConsentRequestRecord): Promise<void> {
|
|
556
|
+
if (this.consentRepository) {
|
|
557
|
+
await this.consentRepository.save(record);
|
|
558
|
+
} else {
|
|
559
|
+
this.pendingConsents.set(record.requestId, { ...record });
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
/**
|
|
564
|
+
* Swap the consent persistence backend at runtime.
|
|
565
|
+
* Useful for applications that want to wire a custom repository after
|
|
566
|
+
* the service has been constructed (e.g., demos supplying a filesystem store).
|
|
567
|
+
*/
|
|
568
|
+
setConsentRepository(repository?: ConsentRepository): void {
|
|
569
|
+
this.consentRepository = repository;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/**
|
|
573
|
+
* Validate user ownership of a target wallet or capability
|
|
574
|
+
* Prefers capability-based validation over legacy ContextWalletService
|
|
575
|
+
*
|
|
576
|
+
* @param userAddress - User's address
|
|
577
|
+
* @param targetWallet - Target wallet address
|
|
578
|
+
* @param appId - Optional app ID for capability lookup
|
|
579
|
+
* @returns True if user has ownership/access
|
|
580
|
+
*/
|
|
581
|
+
private async validateOwnership(
|
|
582
|
+
userAddress: string,
|
|
583
|
+
targetWallet: string,
|
|
584
|
+
appId?: string
|
|
585
|
+
): Promise<boolean> {
|
|
586
|
+
// Prefer capability-based validation (new architecture)
|
|
587
|
+
if (this.capabilityService && appId) {
|
|
588
|
+
return await this.capabilityService.hasCapability(userAddress, appId);
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
// Fall back to legacy ContextWalletService validation
|
|
592
|
+
if (this.contextWalletService) {
|
|
593
|
+
return await this.contextWalletService.validateAccess(targetWallet, userAddress);
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// If neither service is available, skip validation (trust caller)
|
|
597
|
+
return true;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
private async updateConsentStatus(params: {
|
|
601
|
+
requesterWallet: string;
|
|
602
|
+
targetWallet: string;
|
|
603
|
+
newStatus: ConsentStatus;
|
|
604
|
+
updatedAt: number;
|
|
605
|
+
requestId?: string;
|
|
606
|
+
}): Promise<void> {
|
|
607
|
+
const normalizedRequester = normalizeSuiAddress(params.requesterWallet);
|
|
608
|
+
const normalizedTarget = normalizeSuiAddress(params.targetWallet);
|
|
609
|
+
|
|
610
|
+
if (this.consentRepository) {
|
|
611
|
+
if (params.requestId) {
|
|
612
|
+
await this.consentRepository.updateStatus(params.requestId, params.newStatus, params.updatedAt);
|
|
613
|
+
} else {
|
|
614
|
+
const pendingRecords = await this.consentRepository.listByTarget(normalizedTarget, 'pending');
|
|
615
|
+
await Promise.all(
|
|
616
|
+
pendingRecords
|
|
617
|
+
.filter((record) => record.requesterWallet === normalizedRequester)
|
|
618
|
+
.map((record) =>
|
|
619
|
+
this.consentRepository!.updateStatus(record.requestId, params.newStatus, params.updatedAt),
|
|
620
|
+
),
|
|
621
|
+
);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
for (const [requestId, record] of this.pendingConsents.entries()) {
|
|
626
|
+
if (record.requesterWallet === normalizedRequester && record.targetWallet === normalizedTarget) {
|
|
627
|
+
const updatedRecord: ConsentRequestRecord = {
|
|
628
|
+
...record,
|
|
629
|
+
status: params.newStatus,
|
|
630
|
+
updatedAt: params.updatedAt,
|
|
631
|
+
};
|
|
632
|
+
this.pendingConsents.set(requestId, updatedRecord);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
636
|
}
|