feat(webapp): add multiple environment API key management - #4390
feat(webapp): add multiple environment API key management#4390carderne wants to merge 6 commits into
Conversation
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
37de624 to
6b31535
Compare
6aba247 to
2579113
Compare
@trigger.dev/build
trigger.dev
@trigger.dev/core
@trigger.dev/python
@trigger.dev/react-hooks
@trigger.dev/redis-worker
@trigger.dev/rsc
@trigger.dev/schema-to-json
@trigger.dev/sdk
commit: |
6b31535 to
a8e673e
Compare
2579113 to
dbca394
Compare
a8e673e to
24771ce
Compare
646ba1e to
d60d5a5
Compare
The API key policy methods are optional on the plugin-facing controller contract, so `Pick` over it yields optional members that these call sites would have to guard. Both already receive the LazyController singleton, which has substituted its fail-closed defaults, so point them at HostRbacController and keep the call sites guard-free.
Require both the global issuance switch and organization rollout flag before creating additional keys, while leaving existing credentials available for use and revocation. Show nullable creators and identify SDK v4.5.8 as the first compatible public-token version.
d60d5a5 to
ba81e5e
Compare
Record bounded outcomes for additional key creation, policy preparation, revocation, and public-token minting.
| prismaClient.organization.findUnique({ | ||
| where: { id: organizationId }, | ||
| select: { featureFlags: true }, | ||
| }), |
There was a problem hiding this comment.
🟡 API key issuance check uses a Prisma lookup the repo forbids
The organization lookup that decides whether additional API keys can be issued uses prismaClient.organization.findUnique (apps/webapp/app/services/additionalApiKeyIssuance.server.ts:13), which the repository's mandatory webapp rule prohibits in favor of findFirst.
Impact: Violates the documented Prisma query convention meant to avoid the findUnique DataLoader batching bugs.
Rule reference
apps/webapp/CLAUDE.md states under "Prisma Query Patterns": "Always use findFirst instead of findUnique." This is a newly introduced call in apps/webapp/app/services/additionalApiKeyIssuance.server.ts:12-16. The established alternative used elsewhere in the webapp is findFirst.
| prismaClient.organization.findUnique({ | |
| where: { id: organizationId }, | |
| select: { featureFlags: true }, | |
| }), | |
| prismaClient.organization.findFirst({ | |
| where: { id: organizationId }, | |
| select: { featureFlags: true }, | |
| }), |
Was this helpful? React with 👍 or 👎 to provide feedback.
| const keyEnvironmentId = environment.parentEnvironmentId ?? environment.id; | ||
|
|
||
| const [keyEnvironment, vercelIntegration] = await Promise.all([ | ||
| this.#prismaClient.runtimeEnvironment.findUniqueOrThrow({ |
There was a problem hiding this comment.
🟡 API keys presenter uses a Prisma lookup the repo forbids
The presenter loads the key environment with runtimeEnvironment.findUniqueOrThrow (apps/webapp/app/presenters/v3/ApiKeysPresenter.server.ts:72), whereas the repository's mandatory webapp rule requires avoiding the findUnique family; the established equivalent used across presenters is findFirstOrThrow.
Impact: Violates the documented Prisma query convention intended to avoid the findUnique DataLoader batching bugs.
Rule reference
apps/webapp/CLAUDE.md "Prisma Query Patterns": "Always use findFirst instead of findUnique." Many presenters in apps/webapp/app/presenters/v3/ already use findFirstOrThrow for the throwing variant, so the fix is a drop-in replacement.
| this.#prismaClient.runtimeEnvironment.findUniqueOrThrow({ | |
| this.#prismaClient.runtimeEnvironment.findFirstOrThrow({ |
Was this helpful? React with 👍 or 👎 to provide feedback.
| function expirationTimestamp(expirationTime: string | number, now: number): number | undefined { | ||
| if (typeof expirationTime === "number") { | ||
| return expirationTime; | ||
| } | ||
|
|
||
| const match = RELATIVE_TIME_PATTERN.exec(expirationTime); | ||
| if (!match || (match[4] && match[1])) { | ||
| return undefined; | ||
| } | ||
|
|
||
| const value = Number.parseFloat(match[2]!); | ||
| const unit = match[3]!.toLowerCase(); | ||
| const unitSeconds = unit.startsWith("s") | ||
| ? 1 | ||
| : unit.startsWith("m") | ||
| ? 60 | ||
| : unit.startsWith("h") | ||
| ? 60 * 60 | ||
| : unit.startsWith("d") | ||
| ? 24 * 60 * 60 | ||
| : unit.startsWith("w") | ||
| ? 7 * 24 * 60 * 60 | ||
| : 365.25 * 24 * 60 * 60; | ||
| const relativeSeconds = Math.round(value * unitSeconds); | ||
| const isPast = match[1] === "-" || match[4]?.toLowerCase() === "ago"; | ||
|
|
||
| return now + (isPast ? -relativeSeconds : relativeSeconds); | ||
| } |
There was a problem hiding this comment.
🔍 Numeric expirationTime is treated as absolute epoch seconds
In handlePublicTokenRequest, expirationTimestamp returns a numeric expirationTime unchanged (apps/webapp/app/services/publicTokens.server.ts:27-28) and then compares it against now = Math.floor(Date.now()/1000). This means a numeric input must be an absolute epoch value in seconds; a client that sends milliseconds (e.g. Date.now()) would fail the 30-day cap check with a 400. This matches jose's own numeric convention so it is likely intentional, but worth confirming the SDK sends seconds, not ms, for the numeric case.
Was this helpful? React with 👍 or 👎 to provide feedback.
Summary
Projects can create, inspect, expire, and revoke multiple API keys for each environment. Plaintext values are shown only at creation; stored credentials are hashed and the API keys page displays only an obfuscated suffix afterward.
Self-hosted installations support full-access additional keys by default. Authorization extensions can provide additional access presets and optional task selection. Additional keys can also mint scoped public access tokens through the Trigger.dev API without receiving the environment signing key.
Feature notes
_ak_keys.Deployment notes
Deploy the management UI and public-token endpoint with new key creation disabled. Enable creation for selected organizations after the authentication path and released SDK have been verified, then expand availability gradually.
Revoking an API key prevents new bearer requests and new token minting. Public tokens already minted by that key remain valid until their own expiration because they are signed by the environment signing key.
TODO
partially accept items before a validation or authorization error.
Follow-ups
_ak_key.