@@ -513,15 +513,15 @@ export const loadedFirstPartyClient = (
513513 readonly grant : OAuthGrant ;
514514 readonly clientId : string ;
515515 readonly clientSecret : string ;
516- readonly resource : null ;
516+ readonly resource : string | null ;
517517} => ( {
518518 slug : String ( firstPartyOAuthClientSlug ( config . name ) ) ,
519519 authorizationUrl : config . authorizationUrl ,
520520 tokenUrl : config . tokenUrl ,
521521 grant : "authorization_code" ,
522522 clientId : config . clientId ,
523523 clientSecret : config . clientSecret ,
524- resource : null ,
524+ resource : config . resource ?? null ,
525525} ) ;
526526
527527export const makeOAuthService = ( deps : OAuthServiceDeps ) : OAuthService => {
@@ -1026,7 +1026,7 @@ export const makeOAuthService = (deps: OAuthServiceDeps): OAuthService => {
10261026 grant : "authorization_code" ,
10271027 authorizationUrl : config . authorizationUrl ,
10281028 tokenUrl : config . tokenUrl ,
1029- resource : null ,
1029+ resource : config . resource ?? null ,
10301030 clientId : config . clientId ,
10311031 origin : {
10321032 kind : "first_party" ,
@@ -1213,25 +1213,32 @@ export const makeOAuthService = (deps: OAuthServiceDeps): OAuthService => {
12131213 } ) ,
12141214 ) ,
12151215 ) ;
1216+ const firstParty = firstPartyFlow ? firstPartyBySlug . get ( String ( input . client ) ) : undefined ;
12161217 const requestedScopes =
12171218 scopePolicy . kind === "discover"
1218- ? yield * discoverScopesForResource ( client . resource ) . pipe (
1219- Effect . mapError (
1220- ( cause ) =>
1221- new OAuthStartError ( {
1222- // oxlint-disable-next-line executor/no-unknown-error-message -- boundary: OAuthDiscoveryError carries a typed `message` field
1223- message : `Failed to discover OAuth scopes: ${ cause . message } ` ,
1224- } ) ,
1225- ) ,
1226- )
1219+ ? yield * ( ( ) => {
1220+ const discovered = discoverScopesForResource ( client . resource ) . pipe (
1221+ Effect . mapError (
1222+ ( cause ) =>
1223+ new OAuthStartError ( {
1224+ // oxlint-disable-next-line executor/no-unknown-error-message -- boundary: OAuthDiscoveryError carries a typed `message` field
1225+ message : `Failed to discover OAuth scopes: ${ cause . message } ` ,
1226+ } ) ,
1227+ ) ,
1228+ ) ;
1229+ if ( firstParty ?. allowedScopes === undefined ) return discovered ;
1230+ const allowed = new Set ( firstParty . allowedScopes ) ;
1231+ return discovered . pipe (
1232+ Effect . map ( ( scopes ) => scopes . filter ( ( scope ) => allowed . has ( scope ) ) ) ,
1233+ ) ;
1234+ } ) ( )
12271235 : dedupeScopes ( scopePolicy . scopes ) ;
12281236
12291237 // An explicitly scope-limited first-party app is an authorization
1230- // boundary, not picker decoration. Endpoint matching can associate one
1231- // Google client with every Google API, so enforce the complete requested
1232- // set here before persisting an OAuth session or redirecting the browser .
1238+ // boundary, not picker decoration. Endpoint matching and provider
1239+ // discovery can surface capabilities outside the registered app, so
1240+ // enforce the complete requested set before persisting or redirecting.
12331241 if ( firstPartyFlow ) {
1234- const firstParty = firstPartyBySlug . get ( String ( input . client ) ) ;
12351242 if (
12361243 firstParty !== undefined &&
12371244 ! firstPartyOAuthClientAllowsScopes ( firstParty , requestedScopes )
0 commit comments