@@ -11,6 +11,8 @@ const { packageDirectory } = require('../components/packageComponent.ts');
1111const { encode } = require ( 'cbor-x' ) ;
1212const { getHdbPid } = require ( '../utility/processManagement/processManagement.js' ) ;
1313const { initConfig, getConfigPath } = require ( '../config/configUtils.js' ) ;
14+ const { loadCredentials, saveCredentials } = require ( './cliCredentials.ts' ) ;
15+ const { isJWTExpired } = require ( '../security/tokenAuthentication.ts' ) ;
1416
1517const OP_ALIASES = { deploy : 'deploy_component' , package : 'package_component' } ;
1618
@@ -56,15 +58,32 @@ function buildRequest() {
5658 return req ;
5759}
5860
61+ /**
62+ * Resolves the target URL from various sources.
63+ * @param {Object } req The request object.
64+ * @param {Object } allCredentials Stored credentials.
65+ * @returns {string|null } The resolved target URL.
66+ */
67+ function resolveTarget ( req , allCredentials ) {
68+ return (
69+ req . target ||
70+ process . env . HARPER_CLI_TARGET ||
71+ process . env . CLI_TARGET ||
72+ ( allCredentials && allCredentials . last_target )
73+ ) ;
74+ }
75+
5976/**
6077 * Using a unix domain socket will send a request to hdb operations API server
6178 * @param req
79+ * @param skipResponseLog By default, the response is logged to the console. Set this to true to skip logging it, which can be useful for sensitive responses like login calls!
6280 * @returns {Promise<void> }
6381 */
64- async function cliOperations ( req ) {
65- if ( ! req . target ) {
66- req . target = process . env . HARPER_CLI_TARGET || process . env . CLI_TARGET ;
67- }
82+ async function cliOperations ( req , skipResponseLog = false ) {
83+ require ( 'dotenv' ) . config ( ) ;
84+
85+ const allCredentials = loadCredentials ( ) ;
86+ req . target = resolveTarget ( req , allCredentials ) ;
6887 let target ;
6988 if ( req . target ) {
7089 try {
@@ -76,15 +95,17 @@ async function cliOperations(req) {
7695 throw error ;
7796 }
7897 }
98+ const resolvedTarget = `${ target . protocol } //${ target . hostname } ${ target . port ? ':' + target . port : '' } /` ;
7999 target = {
80100 protocol : target . protocol ,
81101 hostname : target . hostname ,
82102 port : target . port ,
83103 username : req . username || target . username || process . env . HARPER_CLI_USERNAME || process . env . CLI_TARGET_USERNAME ,
84104 password : req . password || target . password || process . env . HARPER_CLI_PASSWORD || process . env . CLI_TARGET_PASSWORD ,
85105 rejectUnauthorized : req . rejectUnauthorized ,
106+ resolvedTarget,
86107 } ;
87- console . error ( `Connecting to ${ target . protocol } // ${ target . hostname } : ${ target . port } ` ) ;
108+ console . error ( `Connecting to ${ resolvedTarget } ` ) ;
88109 } else {
89110 // if we aren't doing a targeted operation (like deploy), we initialize the config and verify that local harper
90111 // is running and that we can communicate with it.
@@ -110,6 +131,46 @@ async function cliOperations(req) {
110131 options . headers = { 'Content-Type' : 'application/json' } ;
111132 if ( target ?. username ) {
112133 options . headers . Authorization = `Basic ${ Buffer . from ( `${ target . username } :${ target . password } ` ) . toString ( 'base64' ) } ` ;
134+ } else if ( allCredentials ) {
135+ let tokens = null ;
136+ let lookupKey = null ;
137+ if ( target && allCredentials . targets ) {
138+ lookupKey = target . resolvedTarget . endsWith ( '/' ) ? target . resolvedTarget : target . resolvedTarget + '/' ;
139+ tokens =
140+ allCredentials . targets [ lookupKey ] ?? allCredentials . targets [ target . resolvedTarget . replace ( / \/ $ / , '' ) ] ?? null ;
141+ } else if ( allCredentials . operation_token ) {
142+ tokens = allCredentials ;
143+ }
144+
145+ if ( tokens ?. operation_token ) {
146+ if ( tokens . refresh_token && isJWTExpired ( tokens . operation_token ) ) {
147+ console . error ( 'Operation token expired, attempting to refresh...' ) ;
148+ try {
149+ const refreshOptions = { ...options } ;
150+ refreshOptions . headers = { ...options . headers , Authorization : `Bearer ${ tokens . refresh_token } ` } ;
151+ const refreshResponse = await httpRequest ( refreshOptions , {
152+ operation : 'refresh_operation_token' ,
153+ } ) ;
154+ if ( refreshResponse . statusCode === 200 ) {
155+ const refreshData = JSON . parse ( refreshResponse . body ) ;
156+ if ( refreshData . operation_token ) {
157+ tokens . operation_token = refreshData . operation_token ;
158+ saveCredentials ( {
159+ target : lookupKey || target ?. resolvedTarget ,
160+ operation_token : tokens . operation_token ,
161+ refresh_token : tokens . refresh_token ,
162+ } ) ;
163+ console . error ( 'Operation token refreshed successfully.' ) ;
164+ }
165+ } else {
166+ console . error ( `Failed to refresh operation token: ${ refreshResponse . statusCode } ` ) ;
167+ }
168+ } catch ( refreshErr ) {
169+ console . error ( `Error refreshing operation token: ${ refreshErr . message } ` ) ;
170+ }
171+ }
172+ options . headers . Authorization = `Bearer ${ tokens . operation_token } ` ;
173+ }
113174 }
114175 if ( req . cborEncode ) {
115176 options . headers [ 'Content-Type' ] = 'application/cbor' ;
@@ -141,7 +202,13 @@ async function cliOperations(req) {
141202 process . exit ( 1 ) ;
142203 }
143204
144- console . log ( responseLog ) ;
205+ if ( ! skipResponseLog ) {
206+ console . log ( responseLog ) ;
207+ }
208+
209+ if ( target ) {
210+ responseData . resolvedTarget = target . resolvedTarget ;
211+ }
145212
146213 return responseData ;
147214 } catch ( err ) {
0 commit comments