@@ -6,7 +6,17 @@ import {
66 OAUTH_REFRESH_LOCK_OPTIONS ,
77 log ,
88} from "./constants.js" ;
9- import { hasUsableOAuthCredential as hasUsableStoredOAuthCredential } from "./credential-state.js" ;
9+ import {
10+ areOAuthCredentialsEquivalent ,
11+ hasUsableOAuthCredential ,
12+ isSafeToAdoptBootstrapOAuthIdentity ,
13+ isSafeToOverwriteStoredOAuthIdentity ,
14+ overlayRuntimeExternalOAuthProfiles ,
15+ shouldBootstrapFromExternalCliCredential ,
16+ shouldPersistRuntimeExternalOAuthProfile ,
17+ shouldReplaceStoredOAuthCredential ,
18+ type RuntimeExternalOAuthProfile ,
19+ } from "./oauth-shared.js" ;
1020import { ensureAuthStoreFile , resolveAuthStorePath , resolveOAuthRefreshLockPath } from "./paths.js" ;
1121import {
1222 ensureAuthProfileStore ,
@@ -76,179 +86,17 @@ export class OAuthManagerRefreshError extends Error {
7686 }
7787}
7888
79- export type RuntimeExternalOAuthProfile = {
80- profileId : string ;
81- credential : OAuthCredential ;
82- persistence ?: "runtime-only" | "persisted" ;
89+ export {
90+ areOAuthCredentialsEquivalent ,
91+ hasUsableOAuthCredential ,
92+ isSafeToAdoptBootstrapOAuthIdentity ,
93+ isSafeToOverwriteStoredOAuthIdentity ,
94+ overlayRuntimeExternalOAuthProfiles ,
95+ shouldBootstrapFromExternalCliCredential ,
96+ shouldPersistRuntimeExternalOAuthProfile ,
97+ shouldReplaceStoredOAuthCredential ,
8398} ;
84-
85- export function areOAuthCredentialsEquivalent (
86- a : OAuthCredential | undefined ,
87- b : OAuthCredential ,
88- ) : boolean {
89- if ( ! a || a . type !== "oauth" ) {
90- return false ;
91- }
92- return (
93- a . provider === b . provider &&
94- a . access === b . access &&
95- a . refresh === b . refresh &&
96- a . expires === b . expires &&
97- a . email === b . email &&
98- a . enterpriseUrl === b . enterpriseUrl &&
99- a . projectId === b . projectId &&
100- a . accountId === b . accountId
101- ) ;
102- }
103-
104- function hasNewerStoredOAuthCredential (
105- existing : OAuthCredential | undefined ,
106- incoming : OAuthCredential ,
107- ) : boolean {
108- return Boolean (
109- existing &&
110- existing . provider === incoming . provider &&
111- Number . isFinite ( existing . expires ) &&
112- ( ! Number . isFinite ( incoming . expires ) || existing . expires > incoming . expires ) ,
113- ) ;
114- }
115-
116- export function shouldReplaceStoredOAuthCredential (
117- existing : OAuthCredential | undefined ,
118- incoming : OAuthCredential ,
119- ) : boolean {
120- if ( ! existing || existing . type !== "oauth" ) {
121- return true ;
122- }
123- if ( areOAuthCredentialsEquivalent ( existing , incoming ) ) {
124- return false ;
125- }
126- return ! hasNewerStoredOAuthCredential ( existing , incoming ) ;
127- }
128-
129- export function hasUsableOAuthCredential (
130- credential : OAuthCredential | undefined ,
131- now = Date . now ( ) ,
132- ) : boolean {
133- return hasUsableStoredOAuthCredential ( credential , { now } ) ;
134- }
135-
136- function normalizeAuthIdentityToken ( value : string | undefined ) : string | undefined {
137- const trimmed = value ?. trim ( ) ;
138- return trimmed ? trimmed : undefined ;
139- }
140-
141- function normalizeAuthEmailToken ( value : string | undefined ) : string | undefined {
142- return normalizeAuthIdentityToken ( value ) ?. toLowerCase ( ) ;
143- }
144-
145- function hasOAuthIdentity ( credential : Pick < OAuthCredential , "accountId" | "email" > ) : boolean {
146- return (
147- normalizeAuthIdentityToken ( credential . accountId ) !== undefined ||
148- normalizeAuthEmailToken ( credential . email ) !== undefined
149- ) ;
150- }
151-
152- function hasMatchingOAuthIdentity (
153- existing : Pick < OAuthCredential , "accountId" | "email" > ,
154- incoming : Pick < OAuthCredential , "accountId" | "email" > ,
155- ) : boolean {
156- const existingAccountId = normalizeAuthIdentityToken ( existing . accountId ) ;
157- const incomingAccountId = normalizeAuthIdentityToken ( incoming . accountId ) ;
158- if ( existingAccountId !== undefined && incomingAccountId !== undefined ) {
159- return existingAccountId === incomingAccountId ;
160- }
161-
162- const existingEmail = normalizeAuthEmailToken ( existing . email ) ;
163- const incomingEmail = normalizeAuthEmailToken ( incoming . email ) ;
164- if ( existingEmail !== undefined && incomingEmail !== undefined ) {
165- return existingEmail === incomingEmail ;
166- }
167-
168- return false ;
169- }
170-
171- export function isSafeToOverwriteStoredOAuthIdentity (
172- existing : OAuthCredential | undefined ,
173- incoming : OAuthCredential ,
174- ) : boolean {
175- if ( ! existing || existing . type !== "oauth" ) {
176- return true ;
177- }
178- if ( existing . provider !== incoming . provider ) {
179- return false ;
180- }
181- if ( areOAuthCredentialsEquivalent ( existing , incoming ) ) {
182- return true ;
183- }
184- if ( ! hasOAuthIdentity ( existing ) ) {
185- return false ;
186- }
187- return hasMatchingOAuthIdentity ( existing , incoming ) ;
188- }
189-
190- export function isSafeToAdoptBootstrapOAuthIdentity (
191- existing : OAuthCredential | undefined ,
192- incoming : OAuthCredential ,
193- ) : boolean {
194- if ( ! existing || existing . type !== "oauth" ) {
195- return true ;
196- }
197- if ( existing . provider !== incoming . provider ) {
198- return false ;
199- }
200- if ( areOAuthCredentialsEquivalent ( existing , incoming ) ) {
201- return true ;
202- }
203- if ( ! hasOAuthIdentity ( existing ) ) {
204- return true ;
205- }
206- return hasMatchingOAuthIdentity ( existing , incoming ) ;
207- }
208-
209- export function shouldBootstrapFromExternalCliCredential ( params : {
210- existing : OAuthCredential | undefined ;
211- imported : OAuthCredential ;
212- now ?: number ;
213- } ) : boolean {
214- const now = params . now ?? Date . now ( ) ;
215- if ( hasUsableOAuthCredential ( params . existing , now ) ) {
216- return false ;
217- }
218- return hasUsableOAuthCredential ( params . imported , now ) ;
219- }
220-
221- export function overlayRuntimeExternalOAuthProfiles (
222- store : AuthProfileStore ,
223- profiles : Iterable < RuntimeExternalOAuthProfile > ,
224- ) : AuthProfileStore {
225- const externalProfiles = Array . from ( profiles ) ;
226- if ( externalProfiles . length === 0 ) {
227- return store ;
228- }
229- const next = structuredClone ( store ) ;
230- for ( const profile of externalProfiles ) {
231- next . profiles [ profile . profileId ] = profile . credential ;
232- }
233- return next ;
234- }
235-
236- export function shouldPersistRuntimeExternalOAuthProfile ( params : {
237- profileId : string ;
238- credential : OAuthCredential ;
239- profiles : Iterable < RuntimeExternalOAuthProfile > ;
240- } ) : boolean {
241- for ( const profile of params . profiles ) {
242- if ( profile . profileId !== params . profileId ) {
243- continue ;
244- }
245- if ( profile . persistence === "persisted" ) {
246- return true ;
247- }
248- return ! areOAuthCredentialsEquivalent ( profile . credential , params . credential ) ;
249- }
250- return true ;
251- }
99+ export type { RuntimeExternalOAuthProfile } ;
252100
253101function hasOAuthCredentialChanged (
254102 previous : Pick < OAuthCredential , "access" | "refresh" | "expires" > ,
0 commit comments