@@ -205,23 +205,22 @@ export async function authenticatePersonalAccessToken(
205205 return ;
206206 }
207207
208- // Best-effort touch. The token can vanish between the findFirst above and
209- // this update if a User cascade-delete happens concurrently (admin delete
210- // flow), so swallow not-found errors rather than 500-ing the auth path.
211- try {
212- await prisma . personalAccessToken . update ( {
213- where : {
214- id : personalAccessToken . id ,
215- } ,
216- data : {
217- lastAccessedAt : new Date ( ) ,
218- } ,
219- } ) ;
220- } catch ( error ) {
221- logger . warn ( "Failed to touch PersonalAccessToken.lastAccessedAt" , {
208+ // Touch lastAccessedAt with updateMany rather than update so a missing
209+ // row (e.g. the PAT was cascade-deleted by a concurrent User delete
210+ // between the findFirst above and this call) yields count = 0 instead
211+ // of throwing. count = 0 means the token no longer exists — treat that
212+ // as an authentication miss rather than handing a userId for a deleted
213+ // user back to callers that don't re-verify the user.
214+ const touchResult = await prisma . personalAccessToken . updateMany ( {
215+ where : { id : personalAccessToken . id } ,
216+ data : { lastAccessedAt : new Date ( ) } ,
217+ } ) ;
218+
219+ if ( touchResult . count === 0 ) {
220+ logger . warn ( "PersonalAccessToken vanished between findFirst and update" , {
222221 personalAccessTokenId : personalAccessToken . id ,
223- error,
224222 } ) ;
223+ return ;
225224 }
226225
227226 const decryptedToken = decryptPersonalAccessToken ( personalAccessToken ) ;
0 commit comments