44 * For full license text, see the license.txt file in the repo root or http://www.apache.org/licenses/LICENSE-2.0
55 */
66import { Flags } from '@oclif/core' ;
7- import { OAuthCommand } from '@salesforce/b2c-tooling-sdk/cli' ;
7+ import { BaseCommand , loadConfig } from '@salesforce/b2c-tooling-sdk/cli' ;
88import { setStoredSession , decodeJWT } from '@salesforce/b2c-tooling-sdk/auth' ;
9+ import { DEFAULT_ACCOUNT_MANAGER_HOST } from '@salesforce/b2c-tooling-sdk' ;
910import { t } from '../../../i18n/index.js' ;
1011
1112/**
@@ -19,7 +20,7 @@ import {t} from '../../../i18n/index.js';
1920 *
2021 * Use --renew to enable automatic token renewal for later use with `auth client renew`.
2122 */
22- export default class AuthClient extends OAuthCommand < typeof AuthClient > {
23+ export default class AuthClient extends BaseCommand < typeof AuthClient > {
2324 static description = t ( 'commands.auth.client.description' , 'Authenticate an API client and save session' ) ;
2425
2526 static examples = [
@@ -30,6 +31,29 @@ export default class AuthClient extends OAuthCommand<typeof AuthClient> {
3031 ] ;
3132
3233 static flags = {
34+ 'client-id' : Flags . string ( {
35+ description : 'Client ID for OAuth' ,
36+ env : 'SFCC_CLIENT_ID' ,
37+ helpGroup : 'AUTH' ,
38+ } ) ,
39+ 'client-secret' : Flags . string ( {
40+ description : 'Client secret for OAuth' ,
41+ env : 'SFCC_CLIENT_SECRET' ,
42+ helpGroup : 'AUTH' ,
43+ } ) ,
44+ 'account-manager-host' : Flags . string ( {
45+ description : `Account Manager hostname for OAuth (default: ${ DEFAULT_ACCOUNT_MANAGER_HOST } )` ,
46+ env : 'SFCC_ACCOUNT_MANAGER_HOST' ,
47+ helpGroup : 'AUTH' ,
48+ } ) ,
49+ 'auth-scope' : Flags . string ( {
50+ description : 'OAuth scopes to request (comma-separated)' ,
51+ env : 'SFCC_OAUTH_SCOPES' ,
52+ multiple : true ,
53+ multipleNonGreedy : true ,
54+ delimiter : ',' ,
55+ helpGroup : 'AUTH' ,
56+ } ) ,
3357 renew : Flags . boolean ( {
3458 char : 'r' ,
3559 description : 'Enable automatic token renewal (stores credentials for later refresh)' ,
@@ -50,6 +74,20 @@ export default class AuthClient extends OAuthCommand<typeof AuthClient> {
5074 } ) ,
5175 } ;
5276
77+ protected override loadConfiguration ( ) {
78+ const scopes = this . flags [ 'auth-scope' ] as string [ ] | undefined ;
79+ return loadConfig (
80+ {
81+ clientId : this . flags [ 'client-id' ] as string | undefined ,
82+ clientSecret : this . flags [ 'client-secret' ] as string | undefined ,
83+ accountManagerHost : this . flags [ 'account-manager-host' ] as string | undefined ,
84+ scopes : scopes && scopes . length > 0 ? scopes : undefined ,
85+ } ,
86+ this . getBaseConfigOptions ( ) ,
87+ this . getPluginSources ( ) ,
88+ ) ;
89+ }
90+
5391 async run ( ) : Promise < void > {
5492 const clientId = this . resolvedConfig . values . clientId ;
5593 const clientSecret = this . resolvedConfig . values . clientSecret ;
@@ -65,19 +103,9 @@ export default class AuthClient extends OAuthCommand<typeof AuthClient> {
65103
66104 const user = this . flags . user ;
67105 const userPassword = this . flags [ 'user-password' ] ;
68- const grantTypeFlag = this . flags [ 'grant-type' ] ;
106+ const grantType = this . resolveGrantType ( this . flags [ 'grant-type' ] , user ) ;
69107 const autoRenew = this . flags . renew ;
70108
71- // Determine grant type: explicit flag > auto-detect from user/password > client_credentials
72- let grantType : string ;
73- if ( grantTypeFlag ) {
74- grantType = grantTypeFlag ;
75- } else if ( user ) {
76- grantType = 'password' ;
77- } else {
78- grantType = 'client_credentials' ;
79- }
80-
81109 if ( grantType === 'password' && ( ! user || ! userPassword ) ) {
82110 this . error (
83111 t (
@@ -87,7 +115,7 @@ export default class AuthClient extends OAuthCommand<typeof AuthClient> {
87115 ) ;
88116 }
89117
90- const accountManagerHost = this . accountManagerHost ;
118+ const accountManagerHost = this . resolvedConfig . values . accountManagerHost ?? DEFAULT_ACCOUNT_MANAGER_HOST ;
91119 const scopes = this . resolvedConfig . values . scopes ;
92120
93121 const grantPayload : Record < string , string > = { grant_type : grantType } ;
@@ -128,14 +156,11 @@ export default class AuthClient extends OAuthCommand<typeof AuthClient> {
128156 if ( ! response . ok ) {
129157 const errorText = await response . text ( ) ;
130158 this . logger . trace ( { method, url, body : errorText } , `[StatefulAuth RESP BODY] ${ method } ${ url } ` ) ;
131- let errorMsg : string ;
132- try {
133- const parsed = JSON . parse ( errorText ) as { error_description ?: string } ;
134- errorMsg = parsed . error_description ?? errorText ;
135- } catch {
136- errorMsg = errorText ;
137- }
138- this . error ( t ( 'commands.auth.client.failed' , 'Authentication failed: {{error}}' , { error : errorMsg } ) ) ;
159+ this . error (
160+ t ( 'commands.auth.client.failed' , 'Authentication failed: {{error}}' , {
161+ error : this . parseErrorMessage ( errorText ) ,
162+ } ) ,
163+ ) ;
139164 }
140165
141166 const data = ( await response . json ( ) ) as {
@@ -148,28 +173,40 @@ export default class AuthClient extends OAuthCommand<typeof AuthClient> {
148173
149174 this . logger . trace ( { method, url, body : data } , `[StatefulAuth RESP BODY] ${ method } ${ url } ` ) ;
150175
151- // Extract user from id_token JWT if issued (matches sfcc-ci behavior)
152- let sessionUser : null | string = null ;
153- if ( data . id_token ) {
154- try {
155- const decoded = decodeJWT ( data . id_token ) ;
156- if ( typeof decoded . payload . sub === 'string' ) {
157- sessionUser = decoded . payload . sub ;
158- }
159- } catch {
160- // Ignore JWT decode errors for id_token
161- }
162- }
163-
164176 setStoredSession ( {
165177 clientId,
166178 accessToken : data . access_token ,
167179 refreshToken : data . refresh_token ?? null ,
168180 renewBase : autoRenew ? credentials : null ,
169- user : sessionUser ,
181+ user : this . extractUser ( data . id_token ) ,
170182 } ) ;
171183
172184 const renewMsg = autoRenew ? ' Auto-renewal enabled.' : '' ;
173185 this . log ( t ( 'commands.auth.client.success' , 'Authentication succeeded.{{renewMsg}}' , { renewMsg} ) ) ;
174186 }
187+
188+ private extractUser ( idToken : string | undefined ) : null | string {
189+ if ( ! idToken ) return null ;
190+ try {
191+ const decoded = decodeJWT ( idToken ) ;
192+ return typeof decoded . payload . sub === 'string' ? decoded . payload . sub : null ;
193+ } catch {
194+ return null ;
195+ }
196+ }
197+
198+ private parseErrorMessage ( errorText : string ) : string {
199+ try {
200+ const parsed = JSON . parse ( errorText ) as { error_description ?: string } ;
201+ return parsed . error_description ?? errorText ;
202+ } catch {
203+ return errorText ;
204+ }
205+ }
206+
207+ private resolveGrantType ( grantTypeFlag : string | undefined , user : string | undefined ) : string {
208+ if ( grantTypeFlag ) return grantTypeFlag ;
209+ if ( user ) return 'password' ;
210+ return 'client_credentials' ;
211+ }
175212}
0 commit comments