File tree Expand file tree Collapse file tree
packages/fxa-auth-server/lib Expand file tree Collapse file tree Original file line number Diff line number Diff line change @@ -31,6 +31,7 @@ const JWT_ACCESS_TOKENS_ENABLED = config.get(
3131const JWT_ACCESS_TOKENS_CLIENT_IDS = new Set (
3232 config . get ( 'oauthServer.jwtAccessTokens.enabledClientIds' )
3333) ;
34+ const SUBSCRIPTIONS_ENABLED = ! ! config . get ( 'subscriptions.enabled' ) ;
3435
3536const UNTRUSTED_CLIENT_ALLOWED_SCOPES = ScopeSet . fromArray ( [
3637 'openid' ,
@@ -264,7 +265,14 @@ exports.generateAccessToken = async function generateAccessToken(grant) {
264265 return accessToken ;
265266 }
266267
267- if ( grant . scope . contains ( 'profile:subscriptions' ) ) {
268+ // Skip when subscriptions are disabled. Mirrors the gate
269+ // `lib/routes/account.ts` already applies before calling
270+ // `capabilityService.subscriptionCapabilities` / `hasFreeAccess` —
271+ // without it, this path drives a chain that ends in
272+ // `planIdsToClientCapabilities` throwing (errno 998) because no
273+ // `CapabilityManager` is registered in Container when subscriptions
274+ // are off.
275+ if ( SUBSCRIPTIONS_ENABLED && grant . scope . contains ( 'profile:subscriptions' ) ) {
268276 const capabilities =
269277 await capabilityService . determineClientVisibleSubscriptionCapabilities (
270278 clientId ,
Original file line number Diff line number Diff line change 22 * License, v. 2.0. If a copy of the MPL was not distributed with this
33 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
44
5- // `grant.js` captures `JWT_ACCESS_TOKENS_ENABLED` and the allow-list at
6- // module load, so the config is fixed once here. validateRequestedGrant
7- // tests don't depend on those values; generateTokens tests do.
5+ // `grant.js` captures `JWT_ACCESS_TOKENS_ENABLED`, the allow-list, and
6+ // `SUBSCRIPTIONS_ENABLED` at module load, so the config is fixed once
7+ // here. validateRequestedGrant tests don't depend on those values;
8+ // generateTokens tests do.
89jest . mock ( '../../config' , ( ) => {
910 const realConfig = jest . requireActual ( '../../config' ) . config ;
1011 return {
@@ -15,6 +16,8 @@ jest.mock('../../config', () => {
1516 return true ;
1617 case 'oauthServer.jwtAccessTokens.enabledClientIds' :
1718 return [ '9876543210' ] ;
19+ case 'subscriptions.enabled' :
20+ return true ;
1821 default :
1922 return realConfig . get ( key ) ;
2023 }
Original file line number Diff line number Diff line change @@ -905,14 +905,14 @@ describe('CapabilityService', () => {
905905
906906 describe ( 'subscriptionCapabilities — email-list merge' , ( ) => {
907907 beforeEach ( ( ) => {
908- // No subscriptions so we isolate the email-list contribution .
909- mockStripeHelper . fetchCustomer = jest . fn ( async ( ) => ( {
910- subscriptions : { data : [ ] } ,
911- } ) ) ;
912- mockStripeHelper . iapPurchasesToPriceIds = jest . fn ( ) . mockReturnValue ( [ ] ) ;
913- mockPlayBilling . userManager . queryCurrentSubscriptions = jest
914- . fn ( )
915- . mockResolvedValue ( [ ] ) ;
908+ // Stub the price-ID lookup so it returns a single placeholder price .
909+ // The merge tests rely on the `priceIdsToClientCapabilities` mock
910+ // returning subscription caps; `planIdsToClientCapabilities`
911+ // short-circuits on an empty input, so we need a non-empty array
912+ // here for the CapabilityManager path to be exercised at all.
913+ jest
914+ . spyOn ( CapabilityService . prototype , 'subscribedPriceIds' )
915+ . mockResolvedValue ( [ 'price_test' ] ) ;
916916 mockCapabilityManager . priceIdsToClientCapabilities = jest
917917 . fn ( )
918918 . mockResolvedValue ( { } ) ;
Original file line number Diff line number Diff line change @@ -799,6 +799,14 @@ export class CapabilityService {
799799 async planIdsToClientCapabilities (
800800 subscribedPrices : string [ ]
801801 ) : Promise < ClientIdCapabilityMap > {
802+ // No prices to resolve → no capabilities. Short-circuit before the
803+ // CapabilityManager guard so callers that pass through with an empty
804+ // list (e.g. `/v1/oauth/token` for an account with no Stripe subs)
805+ // don't hit a 500 just because CMS isn't wired in this environment.
806+ if ( subscribedPrices . length === 0 ) {
807+ return { } ;
808+ }
809+
802810 if ( ! this . capabilityManager ) {
803811 throw error . internalValidationError (
804812 'planIdsToClientCapabilities' ,
You can’t perform that action at this time.
0 commit comments