{"record":{"id":"53317a91cda986f3","repo":"calcom/cal.diy","slug":"invalid-access-token-53317a","errorCode":null,"errorMessage":"Invalid Access token.","messagePattern":"Invalid Access token\\.","errorType":"http","errorClass":"UnauthorizedException","httpStatus":401,"severity":"error","filePath":"apps/api/v2/src/modules/stripe/stripe.service.ts","lineNumber":102,"sourceCode":"  async getStripeAppKeys() {\n    const app = await this.appsRepository.getAppBySlug(\"stripe\");\n\n    const { client_id, client_secret } = stripeKeysResponseSchema.parse(app?.keys);\n\n    if (!client_id) {\n      throw new NotFoundException(\"Stripe app not found\");\n    }\n\n    if (!client_secret) {\n      throw new NotFoundException(\"Stripe app not found\");\n    }\n\n    return { client_id, client_secret };\n  }\n\n  async saveStripeAccount(state: OAuthCallbackState, code: string, userId: number): Promise<{ url: string }> {\n    if (!userId) {\n      throw new UnauthorizedException(\"Invalid Access token.\");\n    }\n\n    const response = await stripeInstance.oauth.token({\n      grant_type: \"authorization_code\",\n      code: code?.toString(),\n    });\n\n    const data: StripeData = { ...response, default_currency: \"\" };\n    if (response[\"stripe_user_id\"]) {\n      const account = await stripeInstance.accounts.retrieve(response[\"stripe_user_id\"]);\n      data[\"default_currency\"] = account.default_currency;\n    }\n\n    const existingCredentials = await this.credentialsRepository.findAllCredentialsByTypeAndUserId(\n      \"stripe_payment\",\n      userId\n    );\n","sourceCodeStart":84,"sourceCodeEnd":120,"githubUrl":"https://github.com/calcom/cal.diy/blob/176037d0afbe572f870a3c702985e7cd83fe6c0c/apps/api/v2/src/modules/stripe/stripe.service.ts#L84-L120","documentation":"Thrown by StripeService.saveStripeAccount if the userId argument is falsy. This is a 401 Unauthorized defense-in-depth guard inside the service; the controller normally resolves userId from the access token and passes a real number, so reaching this throw means the service was called without a valid user.","triggerScenarios":"saveStripeAccount(state, code, userId) is invoked with userId = 0, undefined, or null. In normal flow the controller resolves a userId first; this guard fires only when the service is called directly or the controller passed a falsy value through.","commonSituations":"A code path calls saveStripeAccount directly (bypassing the controller) without resolving a user. A refactor changed the controller to pass an optional userId that can be undefined. A test invokes the service without a user fixture.","solutions":["Always resolve a non-zero userId (via getAccessTokenOwnerId) before calling saveStripeAccount.","Route Stripe connect completion through the StripeController.save endpoint rather than calling the service directly.","Type the service parameter as a branded/non-zero number so a falsy value is a compile error."],"exampleFix":"// before\nawait stripeService.saveStripeAccount(state, code, maybeUserId);\n\n// after\nif (!userId) throw new UnauthorizedException('Sign in required');\nawait stripeService.saveStripeAccount(state, code, userId);","handlingStrategy":"validation","validationCode":"async function resolveUserForStripe(tokensRepository, accessToken) {\n  const userId = await tokensRepository.getAccessTokenOwnerId(accessToken);\n  if (!userId) throw new UnauthorizedException('Sign in required');\n  return userId;\n}","typeGuard":"function isResolvedUserId(userId: number | null | undefined): userId is number {\n  return typeof userId === 'number' && userId > 0;\n}","tryCatchPattern":"try {\n  await stripeService.saveStripeAccount(state, code, userId);\n} catch (e) {\n  if (e.status === 401 && /Invalid Access token/.test(e.message)) {\n    // ensure caller resolves userId before invoking the service\n  } else throw e;\n}","preventionTips":["Always resolve a non-zero userId in the controller before calling saveStripeAccount.","Route Stripe completion through the controller, never call the service directly.","Type the userId parameter so zero/undefined is a compile-time error."],"tags":["stripe","auth","api-v2","defense-in-depth"],"backgroundTag":null,"analyzedSha":"176037d0afbe572f870a3c702985e7cd83fe6c0c","analyzedAt":"2026-08-12T19:12:41.464Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}