1- import { AuditAction , AuditResourceType , recordAudit } from '@sim/audit'
21import { createLogger } from '@sim/logger'
32import { compareStrings } from '@sim/utils/string'
43import type {
@@ -20,14 +19,14 @@ import {
2019 findCredentialGroupProviderFromProviderId ,
2120} from '@/lib/credential-groups/providers'
2221import { listViewerOrganizationAccounts } from '@/lib/credential-groups/viewer-accounts'
23- import {
24- ManagedOAuthCredentialError ,
25- rejectManagedOAuthToken ,
26- resolveManagedOAuthToken ,
27- } from '@/lib/credentials/managed-oauth'
22+ import { ManagedOAuthCredentialError } from '@/lib/credentials/managed-oauth'
2823import { getOwnOrganizationManagedOAuthCredentials } from '@/lib/credentials/organization-managed'
2924import { isKnowledgeMemberAccessAvailable } from '@/lib/knowledge/access/availability'
30- import { recordCredentialAccess } from '@/lib/oauth/token-resolution'
25+ import {
26+ type AccountTokenRead ,
27+ createAccountTokenRead ,
28+ type ProviderTokenInput ,
29+ } from '@/lib/organizations/home/account-token'
3130import {
3231 HOME_CALENDAR_EVENT_LIMIT ,
3332 HOME_EMAIL_THREAD_LIMIT ,
@@ -71,77 +70,6 @@ export const organizationHomeOperations = {
7170 } ) ,
7271} as const
7372
74- interface ProviderTokenInput {
75- context : OrganizationMembershipContext
76- credentialId : string
77- providerId : string
78- }
79-
80- function resolveToken ( { context, credentialId, providerId } : ProviderTokenInput ) {
81- return resolveManagedOAuthToken ( {
82- credentialId,
83- organizationId : context . organizationId ,
84- expectedProviderId : providerId ,
85- requiredScopes : [ ] ,
86- } )
87- }
88-
89- function isTokenRejected ( error : unknown ) {
90- return error instanceof HomeProviderReadError && error . failure === 'token_rejected'
91- }
92-
93- /**
94- * One read with the member's token, and one more with a fresh token if the first is refused. A
95- * concurrent read of the same account may already have rotated it, so the fresh token is resolved
96- * whether or not this call was the one that rejected the old token.
97- */
98- async function readWithToken < T > (
99- input : ProviderTokenInput ,
100- read : ( accessToken : string ) => Promise < T [ ] >
101- ) : Promise < T [ ] > {
102- const { context, credentialId, providerId } = input
103- const { accessToken } = await resolveToken ( input )
104- recordCredentialAccess ( {
105- actorId : context . userId ,
106- workspaceId : null ,
107- resourceId : credentialId ,
108- providerId,
109- credentialType : 'oauth' ,
110- } )
111- try {
112- return await read ( accessToken )
113- } catch ( error ) {
114- if ( ! isTokenRejected ( error ) ) throw error
115- }
116- const rejected = await rejectManagedOAuthToken ( {
117- credentialId,
118- organizationId : context . organizationId ,
119- expectedProviderId : providerId ,
120- requiredScopes : [ ] ,
121- rejectedAccessToken : accessToken ,
122- } )
123- if ( rejected )
124- recordAudit ( {
125- actorId : context . userId ,
126- action : AuditAction . CREDENTIAL_UPDATED ,
127- resourceType : AuditResourceType . CREDENTIAL ,
128- resourceId : credentialId ,
129- description : 'Marked provider-rejected managed OAuth credential as needing reauthorization' ,
130- metadata : {
131- organizationId : context . organizationId ,
132- provider : providerId ,
133- credentialType : 'managed_oauth' ,
134- } ,
135- } )
136- const retry = await resolveToken ( input )
137- try {
138- return await read ( retry . accessToken )
139- } catch ( error ) {
140- if ( isTokenRejected ( error ) ) throw new HomeProviderReadError ( 'needs_reauth' , 401 )
141- throw error
142- }
143- }
144-
14573const REAUTH_ERROR_CODES : ReadonlySet < ManagedOAuthCredentialError [ 'code' ] > = new Set ( [
14674 'MANAGED_CREDENTIAL_NEEDS_REAUTH' ,
14775 'MANAGED_CREDENTIAL_REVOKED' ,
@@ -181,6 +109,7 @@ interface WidgetReadResult<T> {
181109interface ViewerAccountRead extends ProviderTokenInput {
182110 accountId : string | null
183111 readableAccounts : number
112+ withToken : AccountTokenRead
184113}
185114
186115type ViewerAccountReader < T > = ( accessToken : string , account : ViewerAccountRead ) => Promise < T [ ] >
@@ -254,16 +183,16 @@ async function readViewerAccounts<T>(
254183 if ( ! ownedIds . has ( credentialId ) ) return settled ( 'not_offered' )
255184 if ( ! toRead . has ( credentialId ) ) return settled ( 'not_read' )
256185 try {
186+ const tokenInput = { context, credentialId, providerId }
257187 const account = {
258- context,
259- credentialId,
188+ ...tokenInput ,
260189 accountId,
261- providerId,
262190 readableAccounts : toRead . size ,
191+ withToken : createAccountTokenRead ( tokenInput ) ,
263192 }
264193 return settled (
265194 'ready' ,
266- await readWithToken ( account , ( accessToken ) => read ( accessToken , account ) )
195+ await account . withToken ( ( accessToken ) => read ( accessToken , account ) )
267196 )
268197 } catch ( error ) {
269198 return settled ( failedState ( error , { organizationId, credentialId, providerId } ) )
@@ -277,7 +206,7 @@ async function readViewerAccounts<T>(
277206}
278207
279208interface GmailAccountCursor extends GmailThreadCursor {
280- account : ProviderTokenInput
209+ account : ViewerAccountRead
281210}
282211
283212/**
@@ -298,7 +227,7 @@ async function readRemainingGmailThreads(
298227 const cursor = cursors . get ( credentialId )
299228 if ( ! cursor ) return
300229 try {
301- const threads = await readWithToken ( cursor . account , ( accessToken ) =>
230+ const threads = await cursor . account . withToken ( ( accessToken ) =>
302231 readMoreGmailThreads ( { accessToken, signal, cursor, count } )
303232 )
304233 rows . push ( ...threads . map ( ( thread ) => ( { ...thread , credentialId } ) ) )
0 commit comments