Skip to content

App/edfial 388 store and display API client info#18

Merged
edandylytics merged 9 commits intodevelopmentfrom
app/edfial-388-store-and-display-client
Feb 9, 2026
Merged

App/edfial 388 store and display API client info#18
edandylytics merged 9 commits intodevelopmentfrom
app/edfial-388-store-and-display-client

Conversation

@edandylytics
Copy link
Copy Markdown
Collaborator

This PR pulls client info from the access token on external API requests and saves them with the job. We also display the client name in the frontend, in lieu of a user name, for jobs created by the API.

A few design/scope considerations:

  • We record client info on the Job, not the Run, in this PR. I'm open to adding them to the Run, too! I held off because we don't display that field on the run, it'll basically always be the same as the client on the job, and there's some tech debt that would make adding it awkward (but still doable).
  • Tech debt: We set the createdBy user via the request-scoped Prisma client but the equivalent field for the API client we set directly and use the anonymous Prisma client (which is why we pass the Prisma client to the services). I'm not thrilled with this, but feel it's fine. It gets a by more awkward if we were to set these on the run, too, since we have to pass both the prisma client and the api client info through several fn calls... but probably also fine for this PR. I'm letting this simmer for the moment and don't have strong feelings yet on what, if anything, to do to address this (input welcome!)
  • We prefer the client_id claim over azp but accept either. client_id is what's in RFC 9068. Auth0 sends azp if using the Auth0 profile. For Auth0, at least, the same value is used to identify the client, whichever claim is used.
  • The issuer will rarely change, if ever. But I'm saving it since it is part of identifying the client.
  • We're only sharing the client name with the frontend. The users who will see the jobs in the UI are not the same as those who have access to the client creds.

@edandylytics edandylytics requested a review from bhaugeea February 6, 2026 19:34
}

const TEST_AUDIENCE = process.env.EXTERNAL_API_TOKEN_AUDIENCE ?? 'test-audience';
const TEST_AUDIENCE = process.env.OAUTH2_AUDIENCE ?? 'test-audience';
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missed this when updating the env var in an earlier PR, but the default value matched the .env.test value so it didn't impact how the tests ran.

@bhaugeea
Copy link
Copy Markdown

bhaugeea commented Feb 6, 2026

Speaking in response to your description, without reading much of the code yet, here's my opinion on the tech debt and scoping: I suspect that the token user attribution columns (and also earthbeam ones?) should be automatically filled more or less the way app/session user ones are, but I'm not sure and it depends on the longer-term vision for what features become accessible to the token users. If it never gets beyond a couple endpoints touching a couple tables, anything much fancier than this would be silly. So I'm very happy to start small — explicitly providing those values to Prisma — and perhaps it never goes beyond that.

I do see both the run thing1 and inconsistent management of attribution columns2 as tech debt, but I'm happy to see that paid down over years not weeks. I do think it would be nice to develop a clear long-term vision for somewhat consistent management of attribution columns and maybe audit logging, but my only rule for a PR is that it should be an improvement over a world without the PR, and I just don't care that much where the line is drawn between the scopes of this one and some other later one.

Footnotes

  1. I'm happy to not invest unnecessarily in the run abstraction, but I would hope we have somewhat real plans to either factor it out or make it more real.

  2. App session users vs executor vs token users.

Copy link
Copy Markdown

@bhaugeea bhaugeea left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall 100% on board with the simple approach taken here, and there's just one minor bug that shouldn't be too bad to fix. On the side we discussed longer-term vision for handling human/earthbeam/m2m user context in the request pipeline, business logic, and database. It's something that may deserve refactors at some point (general-purpose prisma that handles all of them and injects appropriate context into the db, db triggers that use that context, addition of attribution columns to more tables, universal audit logging, etc.) but there's no reason to jump the gun on it now. This is the right solution for the present moment.

Comment thread app/models/src/dtos/job.dto.ts Outdated
Comment on lines +105 to +113
/** Used internally to compute isApiInitiated - not included in serialized output */
@Expose()
@Exclude({ toPlainOnly: true })
apiClientId: string | null;

@Expose()
get isApiInitiated(): boolean {
return !!this.apiClientId;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't quite seem to work. isApiInitiated is indeed serialized to a JSON property, but then when the class is instantiated in the front end, it becomes a getter again (which always returns false). It's okay when the apiClientName is present because of how the conditions in the display table's ternary are ordered.


it('should return 403 if ANY required scope is missing', async () => {
const token = await signExternalApiToken({ scope }); // has create:jobs but not read:jobs
const token = await signExternalApiToken({ ...tokenPayload, scope: 'read:jobs' }); // has create:jobs but not read:jobs
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the scope needs to be overridden here (and the way it's overridden is kind of opposite the existing comment), because what's different about this test is what's required, not what's granted.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤦 you're 100% right. I must have been tab-accepting too fast when moving around how the token is handled. Thanks for catching that

@edandylytics edandylytics requested a review from bhaugeea February 9, 2026 16:21
Copy link
Copy Markdown

@bhaugeea bhaugeea left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good!

@edandylytics edandylytics merged commit 773f71f into development Feb 9, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants