@@ -25,7 +25,7 @@ import { credentialGroupScopePolicyVersion } from '@/lib/credential-groups/provi
2525import { decryptCredentialGroupProviderConfiguration } from '@/lib/credential-groups/provider-configuration'
2626import { getCredentialGroupProviderAdapter } from '@/lib/credential-groups/provider-registry'
2727import {
28- type CredentialGroupStandardOAuthProvider ,
28+ type CredentialGroupProvider ,
2929 isCredentialGroupProvider ,
3030} from '@/lib/credential-groups/providers'
3131import { credentialGroupScope } from '@/lib/credential-groups/scope'
@@ -87,7 +87,13 @@ function scopesEqual(left: string[], right: string[]): boolean {
8787
8888async function buildOption (
8989 scope : ResourceScope ,
90- option : CredentialGroupOptionInput ,
90+ option : {
91+ provider : CredentialGroupProvider
92+ label : string
93+ required : boolean
94+ slackBotCredentialId ?: string
95+ requiredScopes ?: string [ ]
96+ } ,
9197 credentialGroupId ?: string ,
9298 executor : DbOrTx = db
9399) : Promise < CredentialGroupOptionConfig > {
@@ -395,11 +401,11 @@ export async function ensureWorkspaceAccountsGroup(
395401 }
396402}
397403
398- /** Adds a provider explicitly selected by an organization administrator, preserving all grants . */
404+ /** Adds a provider or extends its required consent during an explicit administrator action . */
399405export async function addOrganizationAccountProvider (
400406 organizationId : string ,
401407 userId : string ,
402- option : { provider : CredentialGroupStandardOAuthProvider ; label : string } ,
408+ option : { provider : CredentialGroupProvider ; label : string ; requiredScopes ?: string [ ] } ,
403409 executor : DbTransaction
404410) : Promise < { groupId : string ; changed : boolean } > {
405411 const scope = { kind : 'organization' , organizationId } as const
@@ -418,11 +424,37 @@ export async function addOrganizationAccountProvider(
418424 `Connected accounts contains duplicate ${ option . label } settings`
419425 )
420426 if ( matching [ 0 ] ) {
421- if ( matching [ 0 ] . status !== 'active' )
427+ const current = matching [ 0 ]
428+ if ( current . status !== 'active' )
422429 throw new OrchestrationError (
423430 'validation' ,
424431 `Enable ${ option . label } in Connected accounts first`
425432 )
433+ if ( option . requiredScopes ) {
434+ const previousScopes =
435+ current . provider === 'slack'
436+ ? resolveSlackManagedUserScopes ( current . requiredScopes )
437+ : current . requiredScopes
438+ const requiredScopes = [ ...new Set ( [ ...previousScopes , ...option . requiredScopes ] ) ]
439+ const scopeVersion = credentialGroupScopePolicyVersion ( requiredScopes )
440+ if ( ! scopesEqual ( requiredScopes , previousScopes ) || scopeVersion !== current . scopeVersion ) {
441+ const [ updated ] = await executor
442+ . update ( credentialGroup )
443+ . set ( {
444+ options : existing . options . map ( ( entry ) =>
445+ entry . id === current . id ? { ...entry , requiredScopes, scopeVersion } : entry
446+ ) ,
447+ updatedAt : new Date ( ) ,
448+ } )
449+ . where (
450+ and ( eq ( credentialGroup . id , group . id ) , resourceScopeCondition ( credentialGroup , scope ) )
451+ )
452+ . returning ( { id : credentialGroup . id } )
453+ if ( ! updated ) throw new Error ( 'Connected accounts policy update returned no row' )
454+ await invalidateOptionGrants ( executor , group . id , [ current . id ] )
455+ return { groupId : group . id , changed : true }
456+ }
457+ }
426458 return { groupId : group . id , changed : group . created }
427459 }
428460 if (
@@ -449,6 +481,27 @@ export async function addOrganizationAccountProvider(
449481 return { groupId : group . id , changed : true }
450482}
451483
484+ async function invalidateOptionGrants (
485+ executor : DbTransaction ,
486+ groupId : string ,
487+ optionIds : string [ ]
488+ ) {
489+ const enrollmentIds = executor
490+ . select ( { id : credentialGroupEnrollment . id } )
491+ . from ( credentialGroupEnrollment )
492+ . where ( eq ( credentialGroupEnrollment . credentialGroupId , groupId ) )
493+ await executor
494+ . update ( credential )
495+ . set ( { managedOauthStatus : 'needs_reauth' , updatedAt : new Date ( ) } )
496+ . where (
497+ and (
498+ eq ( credential . type , 'managed_oauth' ) ,
499+ inArray ( credential . credentialGroupEnrollmentId , enrollmentIds ) ,
500+ inArray ( credential . credentialGroupOptionId , optionIds )
501+ )
502+ )
503+ }
504+
452505/**
453506 * Refuses to remove account options while a knowledge
454507 * connector syncs per member through one of them: the connector would be left
@@ -570,20 +623,7 @@ export async function updateCredentialGroup(
570623
571624 if ( ! updated ) throw new Error ( 'Credential group update returned no row' )
572625 if ( invalidatedOptionIds . length > 0 ) {
573- const enrollmentIds = tx
574- . select ( { id : credentialGroupEnrollment . id } )
575- . from ( credentialGroupEnrollment )
576- . where ( eq ( credentialGroupEnrollment . credentialGroupId , groupId ) )
577- await tx
578- . update ( credential )
579- . set ( { managedOauthStatus : 'needs_reauth' , updatedAt : new Date ( ) } )
580- . where (
581- and (
582- eq ( credential . type , 'managed_oauth' ) ,
583- inArray ( credential . credentialGroupEnrollmentId , enrollmentIds ) ,
584- inArray ( credential . credentialGroupOptionId , invalidatedOptionIds )
585- )
586- )
626+ await invalidateOptionGrants ( tx , groupId , invalidatedOptionIds )
587627 }
588628 return toCredentialGroup ( updated , await listLinkedMcpServers ( updated . id , tx ) )
589629 } )
0 commit comments