@@ -11,6 +11,7 @@ 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, isTokenExpired, saveCredentials } = require ( './cliCredentials.ts' ) ;
1415
1516const OP_ALIASES = { deploy : 'deploy_component' , package : 'package_component' } ;
1617
@@ -56,15 +57,32 @@ function buildRequest() {
5657 return req ;
5758}
5859
60+ /**
61+ * Resolves the target URL from various sources.
62+ * @param {Object } req The request object.
63+ * @param {Object } allCredentials Stored credentials.
64+ * @returns {string|null } The resolved target URL.
65+ */
66+ function resolveTarget ( req , allCredentials ) {
67+ return (
68+ req . target ||
69+ process . env . HARPER_CLI_TARGET ||
70+ process . env . CLI_TARGET ||
71+ ( allCredentials && allCredentials . last_target )
72+ ) ;
73+ }
74+
5975/**
6076 * Using a unix domain socket will send a request to hdb operations API server
6177 * @param req
78+ * @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!
6279 * @returns {Promise<void> }
6380 */
64- async function cliOperations ( req ) {
65- if ( ! req . target ) {
66- req . target = process . env . HARPER_CLI_TARGET || process . env . CLI_TARGET ;
67- }
81+ async function cliOperations ( req , skipResponseLog = false ) {
82+ require ( 'dotenv' ) . config ( ) ;
83+
84+ const allCredentials = loadCredentials ( ) ;
85+ req . target = resolveTarget ( req , allCredentials ) ;
6886 let target ;
6987 if ( req . target ) {
7088 try {
@@ -76,15 +94,17 @@ async function cliOperations(req) {
7694 throw error ;
7795 }
7896 }
97+ const resolvedTarget = `${ target . protocol } //${ target . hostname } ${ target . port ? ':' + target . port : '' } /` ;
7998 target = {
8099 protocol : target . protocol ,
81100 hostname : target . hostname ,
82101 port : target . port ,
83102 username : req . username || target . username || process . env . HARPER_CLI_USERNAME || process . env . CLI_TARGET_USERNAME ,
84103 password : req . password || target . password || process . env . HARPER_CLI_PASSWORD || process . env . CLI_TARGET_PASSWORD ,
85104 rejectUnauthorized : req . rejectUnauthorized ,
105+ resolvedTarget,
86106 } ;
87- console . error ( `Connecting to ${ target . protocol } // ${ target . hostname } : ${ target . port } ` ) ;
107+ console . error ( `Connecting to ${ resolvedTarget } ` ) ;
88108 } else {
89109 // if we aren't doing a targeted operation (like deploy), we initialize the config and verify that local harper
90110 // is running and that we can communicate with it.
@@ -110,6 +130,46 @@ async function cliOperations(req) {
110130 options . headers = { 'Content-Type' : 'application/json' } ;
111131 if ( target ?. username ) {
112132 options . headers . Authorization = `Basic ${ Buffer . from ( `${ target . username } :${ target . password } ` ) . toString ( 'base64' ) } ` ;
133+ } else if ( allCredentials ) {
134+ let tokens = null ;
135+ let lookupKey = null ;
136+ if ( target && allCredentials . targets ) {
137+ lookupKey = target . resolvedTarget . endsWith ( '/' ) ? target . resolvedTarget : target . resolvedTarget + '/' ;
138+ tokens =
139+ allCredentials . targets [ lookupKey ] ?? allCredentials . targets [ target . resolvedTarget . replace ( / \/ $ / , '' ) ] ?? null ;
140+ } else if ( allCredentials . operation_token ) {
141+ tokens = allCredentials ;
142+ }
143+
144+ if ( tokens ?. operation_token ) {
145+ if ( tokens . refresh_token && isTokenExpired ( tokens . operation_token ) ) {
146+ console . error ( 'Operation token expired, attempting to refresh...' ) ;
147+ try {
148+ const refreshOptions = { ...options } ;
149+ refreshOptions . headers = { ...options . headers , Authorization : `Bearer ${ tokens . refresh_token } ` } ;
150+ const refreshResponse = await httpRequest ( refreshOptions , {
151+ operation : 'refresh_operation_token' ,
152+ } ) ;
153+ if ( refreshResponse . statusCode === 200 ) {
154+ const refreshData = JSON . parse ( refreshResponse . body ) ;
155+ if ( refreshData . operation_token ) {
156+ tokens . operation_token = refreshData . operation_token ;
157+ saveCredentials ( {
158+ target : lookupKey || target ?. resolvedTarget ,
159+ operation_token : tokens . operation_token ,
160+ refresh_token : tokens . refresh_token ,
161+ } ) ;
162+ console . error ( 'Operation token refreshed successfully.' ) ;
163+ }
164+ } else {
165+ console . error ( `Failed to refresh operation token: ${ refreshResponse . statusCode } ` ) ;
166+ }
167+ } catch ( refreshErr ) {
168+ console . error ( `Error refreshing operation token: ${ refreshErr . message } ` ) ;
169+ }
170+ }
171+ options . headers . Authorization = `Bearer ${ tokens . operation_token } ` ;
172+ }
113173 }
114174 if ( req . cborEncode ) {
115175 options . headers [ 'Content-Type' ] = 'application/cbor' ;
@@ -141,7 +201,13 @@ async function cliOperations(req) {
141201 process . exit ( 1 ) ;
142202 }
143203
144- console . log ( responseLog ) ;
204+ if ( ! skipResponseLog ) {
205+ console . log ( responseLog ) ;
206+ }
207+
208+ if ( target ) {
209+ responseData . resolvedTarget = target . resolvedTarget ;
210+ }
145211
146212 return responseData ;
147213 } catch ( err ) {
0 commit comments