feat(domain-access): load trusted signer registry
This commit is contained in:
parent
fa2c868e0a
commit
6bd95c76e9
7 changed files with 318 additions and 17 deletions
|
|
@ -12,6 +12,7 @@
|
||||||
"diff": "^7.0.0",
|
"diff": "^7.0.0",
|
||||||
"express": "^5.1.0",
|
"express": "^5.1.0",
|
||||||
"gray-matter": "^4.0.3",
|
"gray-matter": "^4.0.3",
|
||||||
|
"lucide-react": "^0.468.0",
|
||||||
"marked": "^15.0.0",
|
"marked": "^15.0.0",
|
||||||
"simple-git": "^3.27.0"
|
"simple-git": "^3.27.0"
|
||||||
},
|
},
|
||||||
|
|
@ -2352,6 +2353,15 @@
|
||||||
"yallist": "^3.0.2"
|
"yallist": "^3.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/lucide-react": {
|
||||||
|
"version": "0.468.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.468.0.tgz",
|
||||||
|
"integrity": "sha512-6koYRhnM2N0GGZIdXzSeiNwguv1gt/FAjZOiPl76roBi3xKEXa4WmfpxgQwTTL4KipXjefrnf3oV4IsYhi4JFA==",
|
||||||
|
"license": "ISC",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/marked": {
|
"node_modules/marked": {
|
||||||
"version": "15.0.12",
|
"version": "15.0.12",
|
||||||
"resolved": "https://registry.npmjs.org/marked/-/marked-15.0.12.tgz",
|
"resolved": "https://registry.npmjs.org/marked/-/marked-15.0.12.tgz",
|
||||||
|
|
@ -2638,7 +2648,6 @@
|
||||||
"version": "19.2.8",
|
"version": "19.2.8",
|
||||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz",
|
"resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz",
|
||||||
"integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==",
|
"integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"simple-git": "^3.27.0",
|
"simple-git": "^3.27.0",
|
||||||
"gray-matter": "^4.0.3",
|
"gray-matter": "^4.0.3",
|
||||||
|
"lucide-react": "^0.468.0",
|
||||||
"marked": "^15.0.0",
|
"marked": "^15.0.0",
|
||||||
"diff": "^7.0.0"
|
"diff": "^7.0.0"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -6,16 +6,44 @@ import {
|
||||||
evaluateDomainAccess,
|
evaluateDomainAccess,
|
||||||
parseDomainAccessHandoff,
|
parseDomainAccessHandoff,
|
||||||
} from './domain-access.js';
|
} from './domain-access.js';
|
||||||
|
import {
|
||||||
|
parseTrustedManifestSignerRegistry,
|
||||||
|
resolveTrustedManifestSigner,
|
||||||
|
type TrustedManifestSigner,
|
||||||
|
} from './trusted-signer-registry.js';
|
||||||
|
|
||||||
const NOW = 1_786_291_200_000;
|
const NOW = 1_786_291_200_000;
|
||||||
const COMMIT = 'b'.repeat(40);
|
const COMMIT = 'b'.repeat(40);
|
||||||
const { privateKey, publicKey } = generateKeyPairSync('ed25519');
|
const { privateKey, publicKey } = generateKeyPairSync('ed25519');
|
||||||
const trustedSigner = {
|
function trustedSignerFromRegistry(overrides: Record<string, unknown> = {}): TrustedManifestSigner {
|
||||||
algorithm: 'Ed25519',
|
const parsed = parseTrustedManifestSignerRegistry({
|
||||||
publicKeyPem: publicKey.export({ format: 'pem', type: 'spki' }).toString(),
|
registryId: 'GH-AIOS-TRUSTED-DOMAIN-MANIFEST-SIGNERS-001',
|
||||||
repositoryId: 'REPO-014',
|
schema: 'gh-aios.trusted-domain-manifest-signers/v1',
|
||||||
signerId: 'GH-LIGHTHOUSE-001',
|
signers: [{
|
||||||
} as const;
|
algorithm: 'Ed25519',
|
||||||
|
domainIds: ['DOM-FIFTH-0001'],
|
||||||
|
publicKeyPem: publicKey.export({ format: 'pem', type: 'spki' }).toString(),
|
||||||
|
repositoryId: 'REPO-014',
|
||||||
|
signerId: 'GH-LIGHTHOUSE-001',
|
||||||
|
status: 'ACTIVE',
|
||||||
|
...overrides,
|
||||||
|
}],
|
||||||
|
state: 'CURRENT',
|
||||||
|
version: '1.0.0',
|
||||||
|
}, {
|
||||||
|
repositoryId: 'REPO-012',
|
||||||
|
sourceCommit: 'a'.repeat(40),
|
||||||
|
sourceUrl: 'https://guanghulab.com/code/bingshuo/guanghu-ice-heart',
|
||||||
|
});
|
||||||
|
const signer = resolveTrustedManifestSigner(parsed, {
|
||||||
|
domainId: 'DOM-FIFTH-0001',
|
||||||
|
repositoryId: String(overrides.repositoryId ?? 'REPO-014'),
|
||||||
|
signerId: 'GH-LIGHTHOUSE-001',
|
||||||
|
});
|
||||||
|
if (!signer) throw new Error('test_trusted_signer_missing');
|
||||||
|
return signer;
|
||||||
|
}
|
||||||
|
const trustedSigner = trustedSignerFromRegistry();
|
||||||
const manifestPayload = {
|
const manifestPayload = {
|
||||||
domainId: 'DOM-FIFTH-0001',
|
domainId: 'DOM-FIFTH-0001',
|
||||||
repositoryId: 'REPO-014',
|
repositoryId: 'REPO-014',
|
||||||
|
|
@ -147,7 +175,7 @@ test('trusted signer identity and repository are external inputs, not payload au
|
||||||
validHandoff,
|
validHandoff,
|
||||||
'DOM-FIFTH-0001',
|
'DOM-FIFTH-0001',
|
||||||
'LOCAL-001',
|
'LOCAL-001',
|
||||||
{ ...trustedSigner, repositoryId: 'REPO-OTHER' },
|
trustedSignerFromRegistry({ repositoryId: 'REPO-OTHER' }),
|
||||||
NOW,
|
NOW,
|
||||||
), /domain_access_handoff_invalid/);
|
), /domain_access_handoff_invalid/);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,8 @@
|
||||||
import { createHash, verify as verifySignature } from 'node:crypto';
|
import { createHash, verify as verifySignature } from 'node:crypto';
|
||||||
|
import {
|
||||||
|
assertRegisteredTrustedManifestSigner,
|
||||||
|
type TrustedManifestSigner,
|
||||||
|
} from './trusted-signer-registry.js';
|
||||||
|
|
||||||
const MANIFEST_SCHEMA = 'gh-aios.domain-manifest/v1' as const;
|
const MANIFEST_SCHEMA = 'gh-aios.domain-manifest/v1' as const;
|
||||||
const SHA256_PATTERN = /^[a-f0-9]{64}$/;
|
const SHA256_PATTERN = /^[a-f0-9]{64}$/;
|
||||||
|
|
@ -30,13 +34,6 @@ export interface DomainManifestSignedPayload {
|
||||||
sourceCommit: string;
|
sourceCommit: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TrustedManifestSigner {
|
|
||||||
algorithm: 'Ed25519';
|
|
||||||
publicKeyPem: string;
|
|
||||||
repositoryId: string;
|
|
||||||
signerId: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DomainSessionCapability {
|
export interface DomainSessionCapability {
|
||||||
capabilityId: string;
|
capabilityId: string;
|
||||||
domainId: string;
|
domainId: string;
|
||||||
|
|
@ -117,6 +114,7 @@ export function parseDomainAccessHandoff(
|
||||||
trustedSigner: TrustedManifestSigner,
|
trustedSigner: TrustedManifestSigner,
|
||||||
now = Date.now(),
|
now = Date.now(),
|
||||||
): DomainAccessHandoff {
|
): DomainAccessHandoff {
|
||||||
|
assertRegisteredTrustedManifestSigner(trustedSigner);
|
||||||
if (!isRecord(input) || !hasExactKeys(input, ['connectionReceipt', 'manifest', 'sessionCapability'])) invalidHandoff();
|
if (!isRecord(input) || !hasExactKeys(input, ['connectionReceipt', 'manifest', 'sessionCapability'])) invalidHandoff();
|
||||||
const manifest = input.manifest;
|
const manifest = input.manifest;
|
||||||
const capability = input.sessionCapability;
|
const capability = input.sessionCapability;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,104 @@
|
||||||
|
import test from 'node:test';
|
||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import { generateKeyPairSync } from 'node:crypto';
|
||||||
|
import {
|
||||||
|
assertRegisteredTrustedManifestSigner,
|
||||||
|
parseTrustedManifestSignerRegistry,
|
||||||
|
resolveTrustedManifestSigner,
|
||||||
|
} from './trusted-signer-registry.js';
|
||||||
|
|
||||||
|
const COMMIT = 'd'.repeat(40);
|
||||||
|
const SOURCE = {
|
||||||
|
repositoryId: 'REPO-012',
|
||||||
|
sourceCommit: COMMIT,
|
||||||
|
sourceUrl: 'https://guanghulab.com/code/bingshuo/guanghu-ice-heart',
|
||||||
|
} as const;
|
||||||
|
const publicKeyPem = generateKeyPairSync('ed25519').publicKey
|
||||||
|
.export({ format: 'pem', type: 'spki' }).toString();
|
||||||
|
|
||||||
|
function registry(signers: unknown[] = []) {
|
||||||
|
return {
|
||||||
|
registryId: 'GH-AIOS-TRUSTED-DOMAIN-MANIFEST-SIGNERS-001',
|
||||||
|
schema: 'gh-aios.trusted-domain-manifest-signers/v1',
|
||||||
|
signers,
|
||||||
|
state: 'CURRENT',
|
||||||
|
version: '1.0.0',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function signer(overrides: Record<string, unknown> = {}) {
|
||||||
|
return {
|
||||||
|
algorithm: 'Ed25519',
|
||||||
|
domainIds: ['DOM-FIFTH-0001'],
|
||||||
|
publicKeyPem,
|
||||||
|
repositoryId: 'REPO-014',
|
||||||
|
signerId: 'GH-LIGHTHOUSE-001',
|
||||||
|
status: 'ACTIVE',
|
||||||
|
...overrides,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
test('an empty current registry is safe and trusts no signer', () => {
|
||||||
|
const parsed = parseTrustedManifestSignerRegistry(registry(), SOURCE);
|
||||||
|
assert.equal(parsed.signers.length, 0);
|
||||||
|
assert.equal(resolveTrustedManifestSigner(parsed, {
|
||||||
|
domainId: 'DOM-FIFTH-0001',
|
||||||
|
repositoryId: 'REPO-014',
|
||||||
|
signerId: 'GH-LIGHTHOUSE-001',
|
||||||
|
}), null);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('an active Ed25519 SPKI signer resolves only for its exact domain and repository', () => {
|
||||||
|
const parsed = parseTrustedManifestSignerRegistry(registry([signer()]), SOURCE);
|
||||||
|
const trusted = resolveTrustedManifestSigner(parsed, {
|
||||||
|
domainId: 'DOM-FIFTH-0001',
|
||||||
|
repositoryId: 'REPO-014',
|
||||||
|
signerId: 'GH-LIGHTHOUSE-001',
|
||||||
|
});
|
||||||
|
assert.ok(trusted);
|
||||||
|
assert.equal(assertRegisteredTrustedManifestSigner(trusted), trusted);
|
||||||
|
assert.equal(resolveTrustedManifestSigner(parsed, {
|
||||||
|
domainId: 'DOMAIN-OTHER',
|
||||||
|
repositoryId: 'REPO-014',
|
||||||
|
signerId: 'GH-LIGHTHOUSE-001',
|
||||||
|
}), null);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('copied signer fields do not cross the parsed-registry trust boundary', () => {
|
||||||
|
const parsed = parseTrustedManifestSignerRegistry(registry([signer()]), SOURCE);
|
||||||
|
const trusted = resolveTrustedManifestSigner(parsed, {
|
||||||
|
domainId: 'DOM-FIFTH-0001',
|
||||||
|
repositoryId: 'REPO-014',
|
||||||
|
signerId: 'GH-LIGHTHOUSE-001',
|
||||||
|
});
|
||||||
|
assert.ok(trusted);
|
||||||
|
assert.throws(() => assertRegisteredTrustedManifestSigner({ ...trusted }), /trusted_manifest_signer_unregistered/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('revoked signers stay parseable for history but never resolve', () => {
|
||||||
|
const parsed = parseTrustedManifestSignerRegistry(registry([signer({ status: 'REVOKED' })]), SOURCE);
|
||||||
|
assert.equal(resolveTrustedManifestSigner(parsed, {
|
||||||
|
domainId: 'DOM-FIFTH-0001',
|
||||||
|
repositoryId: 'REPO-014',
|
||||||
|
signerId: 'GH-LIGHTHOUSE-001',
|
||||||
|
}), null);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('duplicates, unknown fields and unsupported algorithms fail closed', () => {
|
||||||
|
assert.throws(() => parseTrustedManifestSignerRegistry(registry([signer(), signer()]), SOURCE), /trusted_signer_registry_invalid/);
|
||||||
|
assert.throws(() => parseTrustedManifestSignerRegistry({ ...registry(), extra: true }, SOURCE), /trusted_signer_registry_invalid/);
|
||||||
|
assert.throws(() => parseTrustedManifestSignerRegistry(registry([signer({ algorithm: 'RSA' })]), SOURCE), /trusted_signer_registry_invalid/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('private keys and malformed public keys fail closed', () => {
|
||||||
|
const privateKeyPem = generateKeyPairSync('ed25519').privateKey
|
||||||
|
.export({ format: 'pem', type: 'pkcs8' }).toString();
|
||||||
|
assert.throws(() => parseTrustedManifestSignerRegistry(registry([signer({ publicKeyPem: privateKeyPem })]), SOURCE), /trusted_signer_registry_invalid/);
|
||||||
|
assert.throws(() => parseTrustedManifestSignerRegistry(registry([signer({ publicKeyPem: 'not-a-key' })]), SOURCE), /trusted_signer_registry_invalid/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('registry source provenance requires exact Guanghu HTTPS and a full commit', () => {
|
||||||
|
assert.throws(() => parseTrustedManifestSignerRegistry(registry(), { ...SOURCE, sourceCommit: 'short' }), /trusted_signer_registry_source_invalid/);
|
||||||
|
assert.throws(() => parseTrustedManifestSignerRegistry(registry(), { ...SOURCE, sourceUrl: 'https://example.com/registry.json' }), /trusted_signer_registry_source_invalid/);
|
||||||
|
assert.throws(() => parseTrustedManifestSignerRegistry(registry(), { ...SOURCE, sourceUrl: 'https://user:pass@guanghulab.com/registry.json' }), /trusted_signer_registry_source_invalid/);
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,162 @@
|
||||||
|
import { createPublicKey } from 'node:crypto';
|
||||||
|
|
||||||
|
const REGISTRY_SCHEMA = 'gh-aios.trusted-domain-manifest-signers/v1' as const;
|
||||||
|
const COMMIT_PATTERN = /^[a-f0-9]{40}(?:[a-f0-9]{24})?$/;
|
||||||
|
const IDENTIFIER_PATTERN = /^[A-Z0-9][A-Z0-9._:-]{1,159}$/;
|
||||||
|
const VERSION_PATTERN = /^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)$/;
|
||||||
|
const registeredTrustedSigners = new WeakSet<object>();
|
||||||
|
|
||||||
|
export interface TrustedManifestSigner {
|
||||||
|
algorithm: 'Ed25519';
|
||||||
|
domainIds: readonly string[];
|
||||||
|
publicKeyPem: string;
|
||||||
|
repositoryId: string;
|
||||||
|
signerId: string;
|
||||||
|
status: 'ACTIVE' | 'REVOKED';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TrustedSignerRegistrySource {
|
||||||
|
repositoryId: 'REPO-012';
|
||||||
|
sourceCommit: string;
|
||||||
|
sourceUrl: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TrustedManifestSignerRegistry {
|
||||||
|
registryId: 'GH-AIOS-TRUSTED-DOMAIN-MANIFEST-SIGNERS-001';
|
||||||
|
schema: typeof REGISTRY_SCHEMA;
|
||||||
|
signers: readonly TrustedManifestSigner[];
|
||||||
|
source: Readonly<TrustedSignerRegistrySource>;
|
||||||
|
state: 'CURRENT';
|
||||||
|
version: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TrustedSignerLookup {
|
||||||
|
domainId: string;
|
||||||
|
repositoryId: string;
|
||||||
|
signerId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function invalidRegistry(): never {
|
||||||
|
throw new Error('trusted_signer_registry_invalid');
|
||||||
|
}
|
||||||
|
|
||||||
|
function invalidSource(): never {
|
||||||
|
throw new Error('trusted_signer_registry_source_invalid');
|
||||||
|
}
|
||||||
|
|
||||||
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||||
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasExactKeys(record: Record<string, unknown>, keys: string[]): boolean {
|
||||||
|
const expected = [...keys].sort();
|
||||||
|
const actual = Object.keys(record).sort();
|
||||||
|
return actual.length === expected.length && actual.every((key, index) => key === expected[index]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isIdentifier(value: unknown): value is string {
|
||||||
|
return typeof value === 'string' && IDENTIFIER_PATTERN.test(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseSource(source: TrustedSignerRegistrySource): Readonly<TrustedSignerRegistrySource> {
|
||||||
|
if (!isRecord(source)
|
||||||
|
|| !hasExactKeys(source, ['repositoryId', 'sourceCommit', 'sourceUrl'])
|
||||||
|
|| source.repositoryId !== 'REPO-012'
|
||||||
|
|| typeof source.sourceCommit !== 'string'
|
||||||
|
|| !COMMIT_PATTERN.test(source.sourceCommit)
|
||||||
|
|| typeof source.sourceUrl !== 'string') invalidSource();
|
||||||
|
let url: URL;
|
||||||
|
try {
|
||||||
|
url = new URL(source.sourceUrl);
|
||||||
|
} catch {
|
||||||
|
invalidSource();
|
||||||
|
}
|
||||||
|
if (url.protocol !== 'https:'
|
||||||
|
|| url.hostname !== 'guanghulab.com'
|
||||||
|
|| url.port
|
||||||
|
|| url.username
|
||||||
|
|| url.password
|
||||||
|
|| url.search
|
||||||
|
|| url.hash) invalidSource();
|
||||||
|
return Object.freeze({ ...source });
|
||||||
|
}
|
||||||
|
|
||||||
|
function parsePublicKey(value: unknown): string {
|
||||||
|
if (typeof value !== 'string'
|
||||||
|
|| value.length > 4096
|
||||||
|
|| value.includes('PRIVATE KEY')
|
||||||
|
|| !value.startsWith('-----BEGIN PUBLIC KEY-----\n')
|
||||||
|
|| !value.endsWith('-----END PUBLIC KEY-----\n')) invalidRegistry();
|
||||||
|
try {
|
||||||
|
const key = createPublicKey({ format: 'pem', key: value });
|
||||||
|
if (key.asymmetricKeyType !== 'ed25519') invalidRegistry();
|
||||||
|
} catch {
|
||||||
|
invalidRegistry();
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseSigner(input: unknown): TrustedManifestSigner {
|
||||||
|
if (!isRecord(input)
|
||||||
|
|| !hasExactKeys(input, ['algorithm', 'domainIds', 'publicKeyPem', 'repositoryId', 'signerId', 'status'])
|
||||||
|
|| input.algorithm !== 'Ed25519'
|
||||||
|
|| !isIdentifier(input.repositoryId)
|
||||||
|
|| !isIdentifier(input.signerId)
|
||||||
|
|| (input.status !== 'ACTIVE' && input.status !== 'REVOKED')
|
||||||
|
|| !Array.isArray(input.domainIds)
|
||||||
|
|| input.domainIds.length === 0
|
||||||
|
|| input.domainIds.some(domainId => !isIdentifier(domainId))
|
||||||
|
|| new Set(input.domainIds).size !== input.domainIds.length) invalidRegistry();
|
||||||
|
const signer: TrustedManifestSigner = Object.freeze({
|
||||||
|
algorithm: 'Ed25519',
|
||||||
|
domainIds: Object.freeze([...input.domainIds] as string[]),
|
||||||
|
publicKeyPem: parsePublicKey(input.publicKeyPem),
|
||||||
|
repositoryId: input.repositoryId,
|
||||||
|
signerId: input.signerId,
|
||||||
|
status: input.status,
|
||||||
|
});
|
||||||
|
registeredTrustedSigners.add(signer);
|
||||||
|
return signer;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseTrustedManifestSignerRegistry(
|
||||||
|
input: unknown,
|
||||||
|
source: TrustedSignerRegistrySource,
|
||||||
|
): TrustedManifestSignerRegistry {
|
||||||
|
if (!isRecord(input)
|
||||||
|
|| !hasExactKeys(input, ['registryId', 'schema', 'signers', 'state', 'version'])
|
||||||
|
|| input.registryId !== 'GH-AIOS-TRUSTED-DOMAIN-MANIFEST-SIGNERS-001'
|
||||||
|
|| input.schema !== REGISTRY_SCHEMA
|
||||||
|
|| input.state !== 'CURRENT'
|
||||||
|
|| typeof input.version !== 'string'
|
||||||
|
|| !VERSION_PATTERN.test(input.version)
|
||||||
|
|| !Array.isArray(input.signers)) invalidRegistry();
|
||||||
|
const signers = input.signers.map(parseSigner);
|
||||||
|
if (new Set(signers.map(signer => signer.signerId)).size !== signers.length) invalidRegistry();
|
||||||
|
return Object.freeze({
|
||||||
|
registryId: input.registryId,
|
||||||
|
schema: input.schema,
|
||||||
|
signers: Object.freeze(signers),
|
||||||
|
source: parseSource(source),
|
||||||
|
state: input.state,
|
||||||
|
version: input.version,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function assertRegisteredTrustedManifestSigner(
|
||||||
|
signer: TrustedManifestSigner,
|
||||||
|
): TrustedManifestSigner {
|
||||||
|
if (!registeredTrustedSigners.has(signer)) throw new Error('trusted_manifest_signer_unregistered');
|
||||||
|
return signer;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveTrustedManifestSigner(
|
||||||
|
registry: TrustedManifestSignerRegistry,
|
||||||
|
lookup: TrustedSignerLookup,
|
||||||
|
): TrustedManifestSigner | null {
|
||||||
|
const signer = registry.signers.find(candidate => candidate.status === 'ACTIVE'
|
||||||
|
&& candidate.signerId === lookup.signerId
|
||||||
|
&& candidate.repositoryId === lookup.repositoryId
|
||||||
|
&& candidate.domainIds.includes(lookup.domainId));
|
||||||
|
return signer ? assertRegisteredTrustedManifestSigner(signer) : null;
|
||||||
|
}
|
||||||
|
|
@ -15,8 +15,7 @@
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./src/*"],
|
"@/*": ["./src/*"],
|
||||||
"@server/*": ["./server/*"],
|
"@server/*": ["./server/*"]
|
||||||
"marked": ["../hololake-desktop/node_modules/marked/lib/marked.esm.js"]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": ["src/**/*", "server/**/*"],
|
"include": ["src/**/*", "server/**/*"],
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue