Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion dist/neox/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,12 @@ export async function runNeoxRegistrationCli(config, argv = process.argv.slice(2
registry,
projectDir,
config,
storage: needsPublication ? createMetadataStorage(config) : undefined,
storage: needsPublication
? createMetadataStorage(config, fetch, {
signerAddress: account.address,
signMessage: (message) => walletClient.signMessage({ account, message }),
})
: undefined,
}, state);
}
if (command === "verify" && !hasMinted(state)) {
Expand Down
8 changes: 7 additions & 1 deletion dist/neox/storage/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,13 @@ export * from "./types.js";
export * from "./inline.js";
export * from "./neofs.js";
export * from "./managed.js";
export * from "./managed-upload-auth.js";
export * from "./user-uri.js";
export declare function metadataBackend(config: AgentProjectConfig): MetadataStorageBackend;
export declare function createMetadataStorage(config: AgentProjectConfig, fetchImpl?: FetchLike): MetadataStorage;
export interface CreateMetadataStorageOptions {
fetchImpl?: FetchLike;
signerAddress?: string;
signMessage?: (message: string) => Promise<string>;
}
export declare function createMetadataStorage(config: AgentProjectConfig, fetchImpl?: FetchLike, options?: CreateMetadataStorageOptions): MetadataStorage;
export declare function uriForStoragePreflight(config: AgentProjectConfig): string | undefined;
23 changes: 21 additions & 2 deletions dist/neox/storage/index.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { assertRegistrationMetadataUri } from "../metadata.js";
import { MANAGED_URI_GAS_ESTIMATE } from "./neofs-uri.js";
import { InlineMetadataStorage } from "./inline.js";
import { createWalletManagedMetadataProtection, managedUploadAuthDisabled, } from "./managed-upload-auth.js";
import { ManagedMetadataStorage, resolveAgentoryApiBaseUrl } from "./managed.js";
import { NeofsMetadataStorage, neofsPublicUri, validateNeofsStorageConfig } from "./neofs.js";
import { UserUriMetadataStorage } from "./user-uri.js";
export * from "./types.js";
export * from "./inline.js";
export * from "./neofs.js";
export * from "./managed.js";
export * from "./managed-upload-auth.js";
export * from "./user-uri.js";
const BACKENDS = ["managed", "uri", "inline", "neofs"];
export function metadataBackend(config) {
Expand All @@ -17,12 +19,29 @@ export function metadataBackend(config) {
}
return backend;
}
export function createMetadataStorage(config, fetchImpl = fetch) {
export function createMetadataStorage(config, fetchImpl = fetch, options = {}) {
const backend = metadataBackend(config);
if (backend === "inline")
return new InlineMetadataStorage();
if (backend === "managed") {
return new ManagedMetadataStorage({ apiBaseUrl: process.env.AGENTORY_API_BASE_URL, fetchImpl }, fetchImpl);
const apiBaseUrl = resolveAgentoryApiBaseUrl();
let protection;
if (!managedUploadAuthDisabled()) {
if (!options.signerAddress || !options.signMessage) {
throw new Error("Managed Agentory uploads require a connected wallet signer when authorization is enabled.");
}
protection = createWalletManagedMetadataProtection({
apiBaseUrl,
fetchImpl: options.fetchImpl ?? fetchImpl,
signerAddress: options.signerAddress,
signMessage: options.signMessage,
});
}
return new ManagedMetadataStorage({
apiBaseUrl,
fetchImpl: options.fetchImpl ?? fetchImpl,
protection,
}, options.fetchImpl ?? fetchImpl);
}
if (backend === "uri") {
return new UserUriMetadataStorage(config.metadataUri ?? "");
Expand Down
15 changes: 15 additions & 0 deletions dist/neox/storage/managed-upload-auth.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import type { FetchLike } from "./types.js";
import { type ManagedMetadataRequestProtection } from "./managed.js";
export declare const MANAGED_UPLOAD_AUTHORIZATION_SCHEME = "AgentoryUpload";
export declare const MANAGED_UPLOAD_AUTHORIZATION_VERSION = "v1";
export declare function formatManagedUploadAuthorizationHeader(args: {
challengeId: string;
signature: string;
}): string;
export declare function managedUploadAuthDisabled(): boolean;
export declare function createWalletManagedMetadataProtection(args: {
apiBaseUrl?: string;
fetchImpl?: FetchLike;
signerAddress: string;
signMessage(message: string): Promise<string>;
}): ManagedMetadataRequestProtection;
94 changes: 94 additions & 0 deletions dist/neox/storage/managed-upload-auth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { MANAGED_METADATA_PATH } from "../constants.js";
import { ManagedMetadataError, resolveAgentoryApiBaseUrl, } from "./managed.js";
export const MANAGED_UPLOAD_AUTHORIZATION_SCHEME = "AgentoryUpload";
export const MANAGED_UPLOAD_AUTHORIZATION_VERSION = "v1";
export function formatManagedUploadAuthorizationHeader(args) {
return `${MANAGED_UPLOAD_AUTHORIZATION_SCHEME} ${MANAGED_UPLOAD_AUTHORIZATION_VERSION} challenge="${args.challengeId}" signature="${args.signature}"`;
}
export function managedUploadAuthDisabled() {
const raw = process.env.MANAGED_UPLOAD_AUTH_DISABLED ?? process.env.AGENTORY_MANAGED_UPLOAD_AUTH_DISABLED;
return raw === "true" || raw === "1";
}
export function createWalletManagedMetadataProtection(args) {
const apiBaseUrl = resolveAgentoryApiBaseUrl(args.apiBaseUrl);
const fetchImpl = args.fetchImpl ?? fetch;
return {
async apply(headers, context) {
const challengeUrl = `${apiBaseUrl}${MANAGED_METADATA_PATH}/challenge`;
const challengeBody = {
signerAddress: args.signerAddress,
chainId: String(context.chainId),
agentRegistry: context.registry,
agentId: context.agentId.toString(10),
contentHash: context.contentHash,
};
let response;
try {
response = await fetchImpl(challengeUrl, {
method: "POST",
headers: {
accept: "application/json",
"content-type": "application/json",
},
body: JSON.stringify(challengeBody),
});
}
catch (error) {
const detail = error instanceof Error ? error.message : "network request failed";
throw new ManagedMetadataError({
code: "storage_unavailable",
message: `Agentory managed upload challenge could not be reached (${detail}).`,
failureClass: "transport",
retryable: true,
});
}
if (!response.ok) {
throw new ManagedMetadataError({
code: "challenge_rejected",
message: `Agentory refused the managed upload challenge (HTTP ${response.status}).`,
failureClass: "security",
retryable: false,
});
}
let challenge;
try {
challenge = (await response.json());
}
catch {
throw new ManagedMetadataError({
code: "challenge_malformed",
message: "Agentory returned a malformed managed upload challenge response.",
failureClass: "security",
retryable: false,
});
}
if (typeof challenge.challengeId !== "string" ||
!challenge.challengeId.trim() ||
typeof challenge.message !== "string" ||
!challenge.message.trim()) {
throw new ManagedMetadataError({
code: "challenge_malformed",
message: "Agentory returned a malformed managed upload challenge response.",
failureClass: "security",
retryable: false,
});
}
let signature;
try {
signature = await args.signMessage(challenge.message);
}
catch {
throw new ManagedMetadataError({
code: "signing_failed",
message: "Could not sign the managed upload challenge with the connected wallet.",
failureClass: "security",
retryable: false,
});
}
headers.set("authorization", formatManagedUploadAuthorizationHeader({
challengeId: challenge.challengeId,
signature,
}));
},
};
}
8 changes: 7 additions & 1 deletion dist/neox/storage/managed.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,14 @@ export type ManagedMetadataFailureClass = "validation" | "storage" | "security"
* Development and staging currently accept the registration document with no client credential.
* Implementations must not attach a NeoFS write secret or an EVM signing key.
*/
export interface ManagedUploadProtectionContext {
contentHash: string;
chainId: number;
registry: string;
agentId: bigint;
}
export interface ManagedMetadataRequestProtection {
apply(headers: Headers): void | Promise<void>;
apply(headers: Headers, context: ManagedUploadProtectionContext): void | Promise<void>;
}
export declare const environmentManagedMetadataProtection: ManagedMetadataRequestProtection;
export declare class ManagedMetadataError extends Error {
Expand Down
9 changes: 7 additions & 2 deletions dist/neox/storage/managed.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { metadataContentHash } from "../metadata.js";
import { parseCanonicalNeofsAgentUri } from "./neofs-uri.js";
export const environmentManagedMetadataProtection = {
apply() {
// No client credential until the API's CLI protection mechanism is selected.
// Used only when the Agentory API has MANAGED_UPLOAD_AUTH_DISABLED (non-production).
},
};
export class ManagedMetadataError extends Error {
Expand Down Expand Up @@ -92,7 +92,12 @@ export class ManagedMetadataStorage {
accept: "application/json",
"content-type": "application/json",
});
await this.protection.apply(headers);
await this.protection.apply(headers, {
contentHash: metadataContentHash(input.metadata),
chainId: input.chainId,
registry: input.registry,
agentId: input.agentId,
});
const secrets = headerSecrets(headers, this.redactedValues);
const url = managedUploadUrl(this.apiBaseUrl);
let response;
Expand Down
8 changes: 7 additions & 1 deletion src/neox/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,13 @@ export async function runNeoxRegistrationCli(
registry,
projectDir,
config,
storage: needsPublication ? createMetadataStorage(config) : undefined,
storage: needsPublication
? createMetadataStorage(config, fetch, {
signerAddress: account.address,
signMessage: (message) =>
walletClient.signMessage({ account, message }),
})
: undefined,
},
state
);
Expand Down
37 changes: 34 additions & 3 deletions src/neox/storage/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ import { assertRegistrationMetadataUri } from "../metadata.js";
import type { AgentProjectConfig, MetadataStorageBackend } from "../types.js";
import { MANAGED_URI_GAS_ESTIMATE } from "./neofs-uri.js";
import { InlineMetadataStorage } from "./inline.js";
import {
createWalletManagedMetadataProtection,
managedUploadAuthDisabled,
} from "./managed-upload-auth.js";
import { ManagedMetadataStorage, resolveAgentoryApiBaseUrl } from "./managed.js";
import { NeofsMetadataStorage, neofsPublicUri, validateNeofsStorageConfig } from "./neofs.js";
import type { FetchLike, MetadataStorage } from "./types.js";
Expand All @@ -11,6 +15,7 @@ export * from "./types.js";
export * from "./inline.js";
export * from "./neofs.js";
export * from "./managed.js";
export * from "./managed-upload-auth.js";
export * from "./user-uri.js";

const BACKENDS: readonly MetadataStorageBackend[] = ["managed", "uri", "inline", "neofs"];
Expand All @@ -25,16 +30,42 @@ export function metadataBackend(config: AgentProjectConfig): MetadataStorageBack
return backend;
}

export interface CreateMetadataStorageOptions {
fetchImpl?: FetchLike;
signerAddress?: string;
signMessage?: (message: string) => Promise<string>;
}

export function createMetadataStorage(
config: AgentProjectConfig,
fetchImpl: FetchLike = fetch
fetchImpl: FetchLike = fetch,
options: CreateMetadataStorageOptions = {}
): MetadataStorage {
const backend = metadataBackend(config);
if (backend === "inline") return new InlineMetadataStorage();
if (backend === "managed") {
const apiBaseUrl = resolveAgentoryApiBaseUrl();
let protection;
if (!managedUploadAuthDisabled()) {
if (!options.signerAddress || !options.signMessage) {
throw new Error(
"Managed Agentory uploads require a connected wallet signer when authorization is enabled."
);
}
protection = createWalletManagedMetadataProtection({
apiBaseUrl,
fetchImpl: options.fetchImpl ?? fetchImpl,
signerAddress: options.signerAddress,
signMessage: options.signMessage,
});
}
return new ManagedMetadataStorage(
{ apiBaseUrl: process.env.AGENTORY_API_BASE_URL, fetchImpl },
fetchImpl
{
apiBaseUrl,
fetchImpl: options.fetchImpl ?? fetchImpl,
protection,
},
options.fetchImpl ?? fetchImpl
);
}
if (backend === "uri") {
Expand Down
Loading
Loading