@@ -26,6 +26,7 @@ import { generateRandomState as defaultGenerateRandomState } from "./generate-ra
2626import { readStoredAuthState , type OAuthFlowState } from "./state" ;
2727import { getOrCreateTemporaryPreviewAccount } from "./temporary" ;
2828import { exchangeRefreshTokenForAccessToken } from "./token-exchange" ;
29+ import type { AuthConfigStorage } from "./config-file/auth" ;
2930import type { TemporaryPreviewAccount } from "./config-file/temporary" ;
3031import type { OAuthFlowContext } from "./context" ;
3132import type {
@@ -77,12 +78,28 @@ export interface LoginProps {
7778 callbackHost ?: string ;
7879 /** Port the local callback server listens on. Defaults to `8976`. */
7980 callbackPort ?: number ;
81+ /**
82+ * Named auth profile to store the token under. When omitted, the default
83+ * profile (`default.toml`) is used.
84+ * Only for use by 'auth create', not exposed to the user
85+ */
86+ profile ?: string ;
8087}
8188
8289/**
8390 * Public surface returned by {@link createOAuthFlow}.
8491 */
8592export interface OAuthFlowAPI {
93+ /**
94+ * Set the active auth profile for all subsequent storage lookups.
95+ *
96+ * Called once at top of command dispatch by the consumer after resolving the
97+ * active profile. `"default"` if never called.
98+ */
99+ setProfile ( profile : string ) : void ;
100+
101+ getActiveProfile ( ) : string ;
102+
86103 /**
87104 * Open the authorize URL in the user's browser, wait for the callback to be
88105 * hit on the local HTTP server, exchange the code for an access token, and
@@ -91,6 +108,9 @@ export interface OAuthFlowAPI {
91108 * Refuses to start when `ctx.hasEnvCredentials()` returns `true`.
92109 * Refuses to start when the compliance region is `fedramp_high`.
93110 *
111+ * When `props.profile` is set, the token is stored under that profile
112+ * instead of the active one. This is used by `auth create <name>`.
113+ *
94114 * @returns `true` on success, `false` when env credentials are present.
95115 */
96116 login ( props : LoginProps ) : Promise < boolean > ;
@@ -101,8 +121,11 @@ export interface OAuthFlowAPI {
101121 *
102122 * No-op when `ctx.hasEnvCredentials()` returns `true` (env credentials
103123 * cannot be revoked).
124+ *
125+ * When `profile` is passed, operates on that profile instead of the
126+ * active one. This is used by `auth delete <name>`.
104127 */
105- logout ( ) : Promise < void > ;
128+ logout ( profile ?: string ) : Promise < void > ;
106129
107130 /**
108131 * If the user has no stored OAuth token, attempt an interactive login.
@@ -144,6 +167,12 @@ export interface OAuthFlowAPI {
144167 */
145168 requireApiToken ( ) : ApiCredentials ;
146169
170+ /**
171+ * Return the scopes granted to the stored OAuth token for the active
172+ * profile, or `undefined` when no OAuth token is stored.
173+ */
174+ getScopes ( ) : string [ ] | undefined ;
175+
147176 /**
148177 * Establish whether `--temporary` is permitted for this invocation. Called
149178 * once at command dispatch by the consumer. Also drops any temporary account
@@ -190,7 +219,12 @@ export function createOAuthFlow(ctx: OAuthFlowContext): OAuthFlowAPI {
190219 generateRandomState : ctx . generateRandomState ?? defaultGenerateRandomState ,
191220 } ;
192221
193- const storage = ctx . storage ;
222+ let activeProfile = "default" ;
223+
224+ function getStorage ( profile ?: string ) : AuthConfigStorage {
225+ return ctx . storageFactory ( profile ?? activeProfile ) ;
226+ }
227+
194228 const getClientId = ( ) =>
195229 typeof ctx . clientId === "function" ? ctx . clientId ( ) : ctx . clientId ;
196230 const consent = ctx . consent ;
@@ -249,7 +283,7 @@ export function createOAuthFlow(ctx: OAuthFlowContext): OAuthFlowAPI {
249283 generators
250284 ) ;
251285
252- storage . write ( {
286+ getStorage ( props . profile ) . write ( {
253287 oauth_token : oauth . token ?. value ?? "" ,
254288 expiration_time : oauth . token ?. expiry ,
255289 refresh_token : oauth . refreshToken ?. value ,
@@ -264,23 +298,24 @@ export function createOAuthFlow(ctx: OAuthFlowContext): OAuthFlowAPI {
264298 return true ;
265299 }
266300
267- function isRefreshNeeded ( ) : boolean {
301+ function isRefreshNeeded ( profile ?: string ) : boolean {
268302 if ( ctx . hasEnvCredentials ( ) ) {
269303 return false ;
270304 }
271305 const { accessToken } = readStoredAuthState ( {
272306 warningLogger : ctx . logger ,
273- storage,
307+ storage : getStorage ( profile ) ,
274308 } ) ;
275309 return Boolean ( accessToken && new Date ( ) >= new Date ( accessToken . expiry ) ) ;
276310 }
277311
278- async function refreshToken ( ) : Promise < boolean > {
312+ async function refreshToken ( profile ?: string ) : Promise < boolean > {
279313 // `exchangeRefreshTokenForAccessToken` reads the refresh token fresh from
280314 // disk on every call, so we always pick up the latest rotation written by a
281315 // sibling Wrangler process. Refresh tokens are single-use, so a long-lived
282316 // process such as `wrangler dev` would otherwise send a stale value and get
283317 // a 401 from the token endpoint.
318+ const storage = getStorage ( profile ) ;
284319
285320 try {
286321 const {
@@ -323,7 +358,7 @@ export function createOAuthFlow(ctx: OAuthFlowContext): OAuthFlowAPI {
323358 // TODO: ask permission before opening browser
324359 const stored = readStoredAuthState ( {
325360 warningLogger : ctx . logger ,
326- storage,
361+ storage : getStorage ( props . profile ) ,
327362 } ) ;
328363 if ( ! stored . accessToken && ! stored . deprecatedApiToken ) {
329364 // Not logged in.
@@ -338,10 +373,10 @@ export function createOAuthFlow(ctx: OAuthFlowContext): OAuthFlowAPI {
338373 return { loggedIn : true } ;
339374 }
340375 return { loggedIn : false , reason : "no-credentials-login-failed" } ;
341- } else if ( isRefreshNeeded ( ) ) {
376+ } else if ( isRefreshNeeded ( props . profile ) ) {
342377 // We're logged in, but the refresh token seems to have expired,
343378 // so let's try to refresh it
344- const didRefresh = await refreshToken ( ) ;
379+ const didRefresh = await refreshToken ( props . profile ) ;
345380 if ( didRefresh ) {
346381 // The token was refreshed, so we're done here
347382 return { loggedIn : true } ;
@@ -362,7 +397,7 @@ export function createOAuthFlow(ctx: OAuthFlowContext): OAuthFlowAPI {
362397 }
363398 }
364399
365- async function logout ( ) : Promise < void > {
400+ async function logout ( profile ?: string ) : Promise < void > {
366401 const clearedTemporary = clearTemporaryAccount ( ) ;
367402
368403 if ( ctx . hasEnvCredentials ( ) ) {
@@ -376,7 +411,7 @@ export function createOAuthFlow(ctx: OAuthFlowContext): OAuthFlowAPI {
376411
377412 const storedRefreshToken = readStoredAuthState ( {
378413 warningLogger : ctx . logger ,
379- storage,
414+ storage : getStorage ( profile ) ,
380415 } ) . refreshToken ;
381416 if ( ! storedRefreshToken ) {
382417 ctx . logger . log (
@@ -400,14 +435,17 @@ export function createOAuthFlow(ctx: OAuthFlowContext): OAuthFlowAPI {
400435 } ,
401436 } ) ;
402437 await response . text ( ) ; // blank text? would be nice if it was something meaningful
403- storage . clear ( ) ;
438+ getStorage ( profile ) . clear ( ) ;
404439 ctx . logger . log ( `Successfully logged out.` ) ;
405440 ctx . purgeOnLoginOrLogout ?.( ) ;
406441 }
407442
408443 async function getOAuthTokenFromLocalState ( ) : Promise < string | undefined > {
409444 // Check if we have an OAuth token
410- let stored = readStoredAuthState ( { warningLogger : ctx . logger , storage } ) ;
445+ let stored = readStoredAuthState ( {
446+ warningLogger : ctx . logger ,
447+ storage : getStorage ( ) ,
448+ } ) ;
411449 if ( ! stored . accessToken ) {
412450 return undefined ;
413451 }
@@ -425,7 +463,10 @@ export function createOAuthFlow(ctx: OAuthFlowContext): OAuthFlowAPI {
425463 return undefined ;
426464 }
427465 // Re-read after the refresh has persisted the new token to disk.
428- stored = readStoredAuthState ( { warningLogger : ctx . logger , storage } ) ;
466+ stored = readStoredAuthState ( {
467+ warningLogger : ctx . logger ,
468+ storage : getStorage ( ) ,
469+ } ) ;
429470 }
430471
431472 return stored . accessToken ?. value ;
@@ -437,7 +478,7 @@ export function createOAuthFlow(ctx: OAuthFlowContext): OAuthFlowAPI {
437478 }
438479
439480 return getAPIToken ( {
440- storage,
481+ storage : getStorage ( ) ,
441482 warningLogger : ctx . logger ,
442483 allowGlobalAuthKey : ctx . allowGlobalAuthKey ,
443484 } ) ;
@@ -449,7 +490,7 @@ export function createOAuthFlow(ctx: OAuthFlowContext): OAuthFlowAPI {
449490 }
450491
451492 return requireApiToken ( {
452- storage,
493+ storage : getStorage ( ) ,
453494 warningLogger : ctx . logger ,
454495 allowGlobalAuthKey : ctx . allowGlobalAuthKey ,
455496 } ) ;
@@ -492,13 +533,31 @@ export function createOAuthFlow(ctx: OAuthFlowContext): OAuthFlowAPI {
492533 return ctx . temporary ?. storage . clear ( ) ?? false ;
493534 }
494535
536+ function setProfile ( profile : string ) : void {
537+ activeProfile = profile ;
538+ }
539+
540+ function getActiveProfile ( ) : string {
541+ return activeProfile ;
542+ }
543+
544+ function getScopes ( ) : string [ ] | undefined {
545+ return readStoredAuthState ( {
546+ warningLogger : ctx . logger ,
547+ storage : getStorage ( ) ,
548+ } ) . scopes ;
549+ }
550+
495551 return {
552+ setProfile,
553+ getActiveProfile,
496554 login,
497555 logout,
498556 loginOrRefreshIfRequired,
499557 getOAuthTokenFromLocalState,
500558 getAPIToken : getAPITokenInternal ,
501559 requireApiToken : requireApiTokenInternal ,
560+ getScopes,
502561 setTemporaryAllowed,
503562 isTemporaryAllowed,
504563 getActiveTemporaryAccount,
0 commit comments