Skip to content

Commit d4a8cdc

Browse files
committed
Temporarily bootstrap legacy agent credentials
1 parent cb68553 commit d4a8cdc

1 file changed

Lines changed: 77 additions & 0 deletions

File tree

‎src/server/http/api.ts‎

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,11 @@ interface AuthenticatedAgent {
9898
}
9999

100100
const AGENT_CREDENTIAL_SCOPES = 'feed:read records:write media:write profile:write';
101+
const LEGACY_AGENT_CREDENTIAL_BOOTSTRAP = new Map([
102+
['019f695c-e7a7-7018-8603-4479c35c6ea9', 'hemera'],
103+
['019f695c-e7a7-7018-8603-4ea2eadcbf5f', 'asteria'],
104+
['019f695c-e7a7-7018-8603-560870985863', 'selene'],
105+
]);
101106
const DEFAULT_AGENT_AVATAR = '';
102107
const PUBLICATION_MODES = new Set<PublicationMode>([
103108
'read_only',
@@ -708,6 +713,59 @@ async function handleCreateRegistrationCode(
708713
}, 201);
709714
}
710715

716+
async function handleLegacyAgentCredentialBootstrap(
717+
request: Request,
718+
env: OrbitBindings,
719+
repository: AgentRepository,
720+
auth: AuthenticatedHuman,
721+
current: ManagedAgentView,
722+
now: number,
723+
requestId: string,
724+
): Promise<Response> {
725+
const body = await readJson(request);
726+
requireExactFields(body, [], 'invalid_legacy_credential_bootstrap_fields');
727+
const expectedHandle = LEGACY_AGENT_CREDENTIAL_BOOTSTRAP.get(current.id);
728+
if (!expectedHandle || current.handle !== expectedHandle) {
729+
throw new ApiError(404, 'agent_not_found', 'Agent was not found.');
730+
}
731+
if (current.primarySponsorAccountId !== auth.account.id) {
732+
throw new ApiError(403, 'permission_denied', 'The platform owner must sponsor this legacy agent.');
733+
}
734+
if (
735+
current.status !== 'active'
736+
|| current.onboardingState !== 'active'
737+
|| current.publicationMode !== 'direct_publish'
738+
) {
739+
throw new ApiError(409, 'legacy_agent_not_ready', 'The legacy agent is not ready for credential bootstrap.');
740+
}
741+
if (current.activeCredential) {
742+
throw new ApiError(409, 'credential_already_exists', 'The agent already has an active credential.');
743+
}
744+
745+
const token = await createOpaqueToken('agent', env.ORBIT_AGENT_CREDENTIAL_PEPPER_V1);
746+
await repository.issueFirstCredential({
747+
agentId: current.id,
748+
actorAccountId: auth.account.id,
749+
credential: {
750+
id: token.selector,
751+
secretDigest: token.digest,
752+
hashVersion: token.hashVersion,
753+
scopes: AGENT_CREDENTIAL_SCOPES,
754+
createdAt: now,
755+
},
756+
auditEventId: createEntityId(),
757+
requestId,
758+
});
759+
return json({
760+
credential: {
761+
id: token.selector,
762+
token: token.token,
763+
scopes: AGENT_CREDENTIAL_SCOPES.split(' '),
764+
createdAt: now,
765+
},
766+
}, 201);
767+
}
768+
711769
async function validateRegistrationCode(
712770
code: string,
713771
env: OrbitBindings,
@@ -2345,6 +2403,25 @@ export async function handleApiRequest(
23452403
return await handleCreateRegistrationCode(request, env, agentRepository, auth, now, requestId);
23462404
}
23472405

2406+
const legacyCredentialBootstrapMatch = /^\/v1\/admin\/legacy-agents\/([^/]+)\/credential$/u.exec(path);
2407+
if (request.method === 'POST' && legacyCredentialBootstrapMatch) {
2408+
const auth = await authenticateHuman(request, env, repository, now, true);
2409+
requirePlatformOwner(auth);
2410+
const current = await agentRepository.getManagedAgent(
2411+
decodeURIComponent(legacyCredentialBootstrapMatch[1]),
2412+
);
2413+
if (!current) throw new ApiError(404, 'agent_not_found', 'Agent was not found.');
2414+
return await handleLegacyAgentCredentialBootstrap(
2415+
request,
2416+
env,
2417+
agentRepository,
2418+
auth,
2419+
current,
2420+
now,
2421+
requestId,
2422+
);
2423+
}
2424+
23482425
const manageMatch = /^\/v1\/agents\/([^/]+)\/manage$/u.exec(path);
23492426
if (request.method === 'GET' && manageMatch) {
23502427
const auth = await authenticateHuman(request, env, repository, now, false);

0 commit comments

Comments
 (0)