remove the tokens service; use the same strategy for target access tokens (bentocache) as with organization access tokens#8212
Conversation
183b276 to
0ef5f17
Compare
There was a problem hiding this comment.
Code Review
This pull request deprecates and removes the standalone tokens service, migrating its token validation, storage, and caching logic directly into the api service as TargetTokenStorage and TargetTokenCache (powered by BentoCache). The usage service has been refactored to consume this cache directly, eliminating inter-service HTTP/tRPC overhead. Feedback on the changes highlights a critical runtime issue where the new TargetTokenCache is not exported in the api package's entry points, and a correctness bug where double-hashing in touchTokenBySecret prevents the last_used_at timestamp from updating in the database.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| "./modules/schema/providers/schema-version-store": "./src/modules/schema/providers/schema-version-store.ts", | ||
| "./modules/token/providers/target-token-storage": "./src/modules/token/providers/target-token-storage.ts" |
There was a problem hiding this comment.
The TargetTokenCache provider is imported by the usage service from @hive/api/modules/token/providers/target-token-cache. However, this subpath is not listed in the exports field of @hive/api/package.json. Because Node.js restricts subpath imports to only those explicitly declared in exports, attempting to import it from another package will fail at runtime. Please add ./modules/token/providers/target-token-cache to the exports.
| "./modules/schema/providers/schema-version-store": "./src/modules/schema/providers/schema-version-store.ts", | |
| "./modules/token/providers/target-token-storage": "./src/modules/token/providers/target-token-storage.ts" | |
| "./modules/schema/providers/schema-version-store": "./src/modules/schema/providers/schema-version-store.ts", | |
| "./modules/token/providers/target-token-cache": "./src/modules/token/providers/target-token-cache.ts", | |
| "./modules/token/providers/target-token-storage": "./src/modules/token/providers/target-token-storage.ts" |
| static touchTokenBySecret(deps: { pool: CommonQueryMethods }) { | ||
| return async function touchTokenBySecret(token: string) { | ||
| await deps.pool.query(psql` | ||
| UPDATE "tokens" | ||
| SET "last_used_at" = NOW() | ||
| WHERE | ||
| "token" = ${hashTargetToken(token)} | ||
| AND "deleted_at" IS NULL | ||
| `); | ||
| }; | ||
| } |
There was a problem hiding this comment.
The touchTokenBySecret method currently takes a token, hashes it using hashTargetToken, and then updates the database. However, in TargetTokenCache.get, the value passed to it is result.token, which is already the hashed token from the database. This causes the token to be double-hashed, meaning the UPDATE query will match 0 rows and last_used_at will never be updated. Changing this to touchTokenByHash avoids redundant hashing (improving performance) and fixes the correctness bug.
| static touchTokenBySecret(deps: { pool: CommonQueryMethods }) { | |
| return async function touchTokenBySecret(token: string) { | |
| await deps.pool.query(psql` | |
| UPDATE "tokens" | |
| SET "last_used_at" = NOW() | |
| WHERE | |
| "token" = ${hashTargetToken(token)} | |
| AND "deleted_at" IS NULL | |
| `); | |
| }; | |
| } | |
| static touchTokenByHash(deps: { pool: CommonQueryMethods }) { | |
| return async function touchTokenByHash(hashedToken: string) { | |
| await deps.pool.query(psql` | |
| UPDATE "tokens" | |
| SET "last_used_at" = NOW() | |
| WHERE | |
| "token" = ${hashedToken} | |
| AND "deleted_at" IS NULL | |
| `); | |
| }; | |
| } |
| }).then(result => { | ||
| if (result) { | ||
| void TargetTokenStorage.touchTokenBySecret({ pool: this.pool })(result.token).catch(() => {}); | ||
| } | ||
| return result | ||
| }); |
There was a problem hiding this comment.
Update this call to use the new touchTokenByHash method with the already computed hashedToken to avoid double-hashing and ensure last_used_at is correctly updated.
| }).then(result => { | |
| if (result) { | |
| void TargetTokenStorage.touchTokenBySecret({ pool: this.pool })(result.token).catch(() => {}); | |
| } | |
| return result | |
| }); | |
| }).then(result => { | |
| if (result) { | |
| void TargetTokenStorage.touchTokenByHash({ pool: this.pool })(hashedToken).catch(() => {}); | |
| } | |
| return result; | |
| }); |
| // touch the token so it has a date | ||
| await tokenStorage.touchTokens({ tokens: [{ token: record.token, date: new Date() }] }); | ||
| const result = await readTokenInfo(token.secret).then(res => res.expectNoGraphQLErrors()); | ||
| await TargetTokenStorage.touchTokenBySecret({ pool })(record.token) | ||
| const result = await readTokenInfo(token).then(res => res.expectNoGraphQLErrors()); |
There was a problem hiding this comment.
Update the test to use touchTokenByHash since record.token is already the hashed token, ensuring the test correctly verifies the last_used_at behavior.
| // touch the token so it has a date | |
| await tokenStorage.touchTokens({ tokens: [{ token: record.token, date: new Date() }] }); | |
| const result = await readTokenInfo(token.secret).then(res => res.expectNoGraphQLErrors()); | |
| await TargetTokenStorage.touchTokenBySecret({ pool })(record.token) | |
| const result = await readTokenInfo(token).then(res => res.expectNoGraphQLErrors()); | |
| // touch the token so it has a date | |
| await TargetTokenStorage.touchTokenByHash({ pool })(record.token) | |
| const result = await readTokenInfo(token).then(res => res.expectNoGraphQLErrors()); |
References
- When refactoring or optimizing SQL queries, write comprehensive integration tests to verify correctness under edge cases to ensure correctness and prevent regressions.
57d9209 to
7a27374
Compare
…kens (bentocache) as with organization access tokens
7a27374 to
5bf6de3
Compare
|
🐋 This PR was built and pushed to the following Docker images: Targets: Platforms: Image Tags: |
Nuke the tokens service and use the same strategy for reading target (legacy) access tokens from postgres/redis cache with a bentocache powered module shared between all services (usage, server) that need to read access tokens.
This removes quite a lot of code and I wanted to do this for a long time. 🙏