diff --git a/.env.example b/.env.example index c94080d..efa18a1 100644 --- a/.env.example +++ b/.env.example @@ -216,6 +216,10 @@ NOCODB_API_TOKEN= # DOC0 document service. URL must be absolute HTTPS; token is a service PAT. DOC0_URL=https://doc0.siax.io DOC0_SERVICE_TOKEN= +# The DOC0 service identity's own actor id (token subject). DOC0 rejects an +# x-actor-id it has no actor_grant for; 0pt keeps the end-user actor in its own +# journal and lets the DOC0 call act as the service. +DOC0_SERVICE_ACTOR_ID= # Maximum accepted upload size (bytes). Default 25 MiB. OPT_MAX_UPLOAD_BYTES=26214400 diff --git a/app/api/0pt/documents/structure/route.ts b/app/api/0pt/documents/structure/route.ts index f20ea52..51689f3 100644 --- a/app/api/0pt/documents/structure/route.ts +++ b/app/api/0pt/documents/structure/route.ts @@ -218,7 +218,7 @@ export async function POST(request: NextRequest) { }).read({ tenantId, projectId, - actorId, + actorId: runtimeConfig.doc0.actorId ?? actorId, requestId: correlationId, receipt: { documentId, diff --git a/app/api/0pt/documents/upload/route.ts b/app/api/0pt/documents/upload/route.ts index ddb5c3e..54ab70d 100644 --- a/app/api/0pt/documents/upload/route.ts +++ b/app/api/0pt/documents/upload/route.ts @@ -163,13 +163,22 @@ export async function POST(request: NextRequest) { const authorize = createProjectAuthorizer(); const sql = getRepositorySql(databaseUrl); + // DOC0 authorizes the SERVICE identity and requires x-actor-id to be an + // actor that identity holds a grant for. The end-user actor is still the + // journal/authorization scope on our side; only the DOC0 call acts as the + // service. + const doc0ActorId = runtimeConfig.doc0.actorId ?? actorId; + const doc0Client = new Doc0UploadClient({ + baseUrl: runtimeConfig.doc0.baseUrl, + serviceToken: runtimeConfig.doc0.serviceToken, + maxBytes: runtimeConfig.doc0.maxUploadBytes, + }); const coordinator = new DocumentUploadCoordinator({ attempts: new PostgresUploadAttemptStore(sql), - doc0: new Doc0UploadClient({ - baseUrl: runtimeConfig.doc0.baseUrl, - serviceToken: runtimeConfig.doc0.serviceToken, - maxBytes: runtimeConfig.doc0.maxUploadBytes, - }), + doc0: { + upload: (input) => doc0Client.upload({ ...input, actorId: doc0ActorId }), + inspect: (input) => doc0Client.inspect({ ...input, actorId: doc0ActorId }), + }, authorize: async (scope) => authorize(scope), maxBytes: runtimeConfig.doc0.maxUploadBytes, }); diff --git a/src/document-workbench/runtime-config.ts b/src/document-workbench/runtime-config.ts index 415f6b5..54088e3 100644 --- a/src/document-workbench/runtime-config.ts +++ b/src/document-workbench/runtime-config.ts @@ -16,7 +16,7 @@ export interface HttpsEndpoint { } export interface DocumentWorkbenchRuntime { - doc0?: HttpsEndpoint & { maxUploadBytes: number }; + doc0?: HttpsEndpoint & { maxUploadBytes: number; actorId?: string }; inf0?: HttpsEndpoint; pay0?: HttpsEndpoint; n0d?: HttpsEndpoint; @@ -84,6 +84,7 @@ export function readDocumentWorkbenchRuntime(env: NodeJS.ProcessEnv = process.en doc0: { ...doc0, maxUploadBytes: positiveInt(env.OPT_MAX_UPLOAD_BYTES, DEFAULT_MAX_UPLOAD_BYTES), + ...(env.DOC0_SERVICE_ACTOR_ID ? { actorId: env.DOC0_SERVICE_ACTOR_ID.trim() } : {}), }, } : {}),