@@ -8,89 +8,60 @@ import { env } from "@/env.mjs";
88import { OrgRole } from "@sourcebot/db" ;
99import { cookies } from "next/headers" ;
1010import { OPTIONAL_PROVIDERS_LINK_SKIPPED_COOKIE_NAME } from "@/lib/constants" ;
11+ import { IntegrationIdentityProviderState } from "@/ee/features/permissionSyncing/types" ;
1112
1213const logger = createLogger ( 'web-ee-permission-syncing-actions' ) ;
1314
14- export const userNeedsToLinkIdentityProvider = async ( ) => sew ( ( ) =>
15+ export const getIntegrationProviderStates = async ( ) => sew ( ( ) =>
1516 withAuthV2 ( async ( { prisma, role, user } ) =>
1617 withMinimumOrgRole ( role , OrgRole . MEMBER , async ( ) => {
1718 const config = await loadConfig ( env . CONFIG_PATH ) ;
18- const identityProviders = config . identityProviders ?? [ ] ;
19-
20- for ( const identityProvider of identityProviders ) {
21- if ( identityProvider . purpose === "integration" ) {
22- // Only check required providers (default to true if not specified)
23- const isRequired = 'required' in identityProvider ? identityProvider . required : true ;
24-
25- if ( ! isRequired ) {
26- continue ;
27- }
28-
29- const linkedAccount = await prisma . account . findFirst ( {
30- where : {
31- provider : identityProvider . provider ,
32- userId : user . id ,
33- } ,
34- } ) ;
35-
36- if ( ! linkedAccount ) {
37- logger . info ( `Required integration identity provider ${ identityProvider . provider } account info not found for user ${ user . id } ` ) ;
38- return true ;
19+ const integrationProviderConfigs = config . identityProviders ?? [ ] ;
20+ const linkedAccounts = await prisma . account . findMany ( {
21+ where : {
22+ userId : user . id ,
23+ provider : {
24+ in : integrationProviderConfigs . map ( p => p . provider )
3925 }
26+ } ,
27+ select : {
28+ provider : true ,
29+ providerAccountId : true
4030 }
41- }
42-
43- return false ;
44- } )
45- )
46- ) ;
47-
48- export const getUnlinkedIntegrationProviders = async ( ) => sew ( ( ) =>
49- withAuthV2 ( async ( { prisma, role, user } ) =>
50- withMinimumOrgRole ( role , OrgRole . MEMBER , async ( ) => {
51- const config = await loadConfig ( env . CONFIG_PATH ) ;
52- const identityProviders = config . identityProviders ?? [ ] ;
53- const unlinkedProviders = [ ] ;
54-
55- for ( const identityProvider of identityProviders ) {
56- if ( identityProvider . purpose === "integration" ) {
57- const linkedAccount = await prisma . account . findFirst ( {
58- where : {
59- provider : identityProvider . provider ,
60- userId : user . id ,
61- } ,
62- } ) ;
31+ } ) ;
6332
64- if ( ! linkedAccount ) {
65- const isRequired = 'required' in identityProvider ? identityProvider . required as boolean : true ;
66- logger . info ( `Integration identity provider ${ identityProvider . provider } not linked for user ${ user . id } ` ) ;
67- unlinkedProviders . push ( {
68- id : identityProvider . provider ,
69- name : identityProvider . provider ,
70- purpose : "integration" as const ,
71- required : isRequired ,
72- } ) ;
73- }
33+ const integrationProviderState : IntegrationIdentityProviderState [ ] = [ ] ;
34+ for ( const integrationProviderConfig of integrationProviderConfigs ) {
35+ if ( integrationProviderConfig . purpose === "integration" ) {
36+ const linkedAccount = linkedAccounts . find (
37+ account => account . provider === integrationProviderConfig . provider
38+ ) ;
39+
40+ const isLinked = ! ! linkedAccount ;
41+ const isRequired = integrationProviderConfig . required ?? true ;
42+ integrationProviderState . push ( {
43+ id : integrationProviderConfig . provider ,
44+ required : isRequired ,
45+ isLinked,
46+ linkedAccountId : linkedAccount ?. providerAccountId
47+ } as IntegrationIdentityProviderState ) ;
7448 }
7549 }
7650
77- return unlinkedProviders ;
51+ return integrationProviderState ;
7852 } )
7953 )
8054) ;
8155
56+
8257export const unlinkIntegrationProvider = async ( provider : string ) => sew ( ( ) =>
8358 withAuthV2 ( async ( { prisma, role, user } ) =>
8459 withMinimumOrgRole ( role , OrgRole . MEMBER , async ( ) => {
8560 const config = await loadConfig ( env . CONFIG_PATH ) ;
8661 const identityProviders = config . identityProviders ?? [ ] ;
8762
88- // Verify this is an integration provider
89- const isIntegrationProvider = identityProviders . some (
90- idp => idp . provider === provider && idp . purpose === "integration"
91- ) ;
92-
93- if ( ! isIntegrationProvider ) {
63+ const providerConfig = identityProviders . find ( idp => idp . provider === provider )
64+ if ( ! providerConfig || ! ( 'purpose' in providerConfig ) || providerConfig . purpose !== "integration" ) {
9465 throw new Error ( "Provider is not an integration provider" ) ;
9566 }
9667
@@ -104,6 +75,14 @@ export const unlinkIntegrationProvider = async (provider: string) => sew(() =>
10475
10576 logger . info ( `Unlinked integration provider ${ provider } for user ${ user . id } . Deleted ${ result . count } account(s).` ) ;
10677
78+ // If we're unlinking a required identity provider then we want to wipe the optional skip cookie if it exists so that we give the
79+ // user the option of linking optional providers in the same link accounts screen
80+ const isRequired = providerConfig . required ?? true ;
81+ if ( isRequired ) {
82+ const cookieStore = await cookies ( ) ;
83+ cookieStore . delete ( OPTIONAL_PROVIDERS_LINK_SKIPPED_COOKIE_NAME ) ;
84+ }
85+
10786 return { success : true , count : result . count } ;
10887 } )
10988 )
0 commit comments