44
55using DevProxy . Abstractions . Proxy ;
66using DevProxy . Abstractions . Utils ;
7+ using DevProxy . State ;
78using Microsoft . Extensions . Logging ;
89using System . CommandLine ;
910using System . CommandLine . Parsing ;
@@ -33,17 +34,94 @@ private void ConfigureCommand()
3334 PrintApiInfo ( outputFormat ) ;
3435 } ) ;
3536
37+ var apiTokenCommand = new Command ( "token" , """
38+ Print the API token of a running Dev Proxy instance.
39+
40+ Examples:
41+ devproxy api token
42+ devproxy api token --pid 12345
43+ devproxy api token --output json
44+
45+ Selects the only running instance. With multiple instances, specify --pid.
46+ Reads credentials for the current user; Dev Proxy must already be running.
47+ Prints the secret to stdout, including when redirected. Errors go to stderr.
48+ JSON output: { "pid": number, "apiUrl": string, "token": string }.
49+ Exit codes: 0 success, 1 instance/credential unavailable, 2 invalid arguments.
50+ """ ) ;
51+ var pidOption = new Option < int ? > ( "--pid" )
52+ {
53+ Description = "Retrieve the token of a specific Dev Proxy instance"
54+ } ;
55+ apiTokenCommand . Add ( pidOption ) ;
56+ apiTokenCommand . SetAction ( async ( parseResult , cancellationToken ) =>
57+ {
58+ var outputFormat = parseResult . GetValueOrDefault < OutputFormat ? > ( DevProxyCommand . OutputOptionName ) ?? OutputFormat . Text ;
59+ return await PrintTokenAsync ( parseResult . GetValue ( pidOption ) , outputFormat , cancellationToken ) ;
60+ } ) ;
61+
3662 this . AddCommands ( new List < Command >
3763 {
38- apiShowCommand
64+ apiShowCommand ,
65+ apiTokenCommand
3966 } . OrderByName ( ) ) ;
4067 }
4168
69+ private static async Task < int > PrintTokenAsync ( int ? pid , OutputFormat outputFormat , CancellationToken cancellationToken )
70+ {
71+ try
72+ {
73+ ProxyInstanceState ? state ;
74+ if ( pid . HasValue )
75+ {
76+ state = await StateManager . LoadStateByPidAsync ( pid . Value , cancellationToken ) ;
77+ }
78+ else
79+ {
80+ var states = await StateManager . LoadAllStatesAsync ( cancellationToken ) ;
81+ if ( states . Count > 1 )
82+ {
83+ await Console . Error . WriteLineAsync ( "Multiple Dev Proxy instances are running. Select one with devproxy api token --pid <PID>:" ) ;
84+ foreach ( var instance in states . OrderBy ( instance => instance . Pid ) )
85+ {
86+ await Console . Error . WriteLineAsync ( $ " { instance . Pid } : { instance . ApiUrl } ") ;
87+ }
88+ return 1 ;
89+ }
90+
91+ state = states . SingleOrDefault ( ) ;
92+ }
93+
94+ if ( state is null )
95+ {
96+ await Console . Error . WriteLineAsync ( pid . HasValue
97+ ? $ "No running Dev Proxy instance with PID { pid . Value } . Run devproxy status to find an instance."
98+ : "Dev Proxy is not running. Start it with devproxy first." ) ;
99+ return 1 ;
100+ }
101+
102+ var token = await File . ReadAllTextAsync ( ApiSecurity . GetTokenFilePath ( state . Pid ) , cancellationToken ) ;
103+ if ( outputFormat == OutputFormat . Json )
104+ {
105+ Console . WriteLine ( JsonSerializer . Serialize ( new { pid = state . Pid , apiUrl = state . ApiUrl , token } , ProxyUtils . JsonSerializerOptions ) ) ;
106+ }
107+ else
108+ {
109+ Console . WriteLine ( token ) ;
110+ }
111+ return 0 ;
112+ }
113+ catch ( Exception ex ) when ( ex is IOException or UnauthorizedAccessException )
114+ {
115+ await Console . Error . WriteLineAsync ( "Unable to read the API token. Restart the selected Dev Proxy instance using the current version and the same user account." ) ;
116+ return 1 ;
117+ }
118+ }
119+
42120 private void PrintApiInfo ( OutputFormat outputFormat )
43121 {
44- var ipAddress = _proxyConfiguration . IPAddress ;
45122 var apiPort = _proxyConfiguration . ApiPort ;
46- var baseUrl = SystemProxyAddress . ToHttpAuthority ( ipAddress , apiPort ) ;
123+ var baseUrl = ApiSecurity . GetApiUrl ( new UriBuilder ( Uri . UriSchemeHttp , _proxyConfiguration . ApiIpAddress , apiPort ) . Uri . AbsoluteUri ) ;
124+ var tokenFilePattern = Path . Combine ( StateManager . GetConfigFolder ( ) , "credentials" , "api-<PID>.token" ) ;
47125
48126 var endpoints = new [ ]
49127 {
@@ -61,7 +139,7 @@ private void PrintApiInfo(OutputFormat outputFormat)
61139 var json = JsonSerializer . Serialize ( new
62140 {
63141 baseUrl ,
64- swaggerUrl = $ " { baseUrl } /swagger/v1/swagger.json" ,
142+ authentication = new { scheme = "Bearer" , header = "Authorization" , tokenFilePattern } ,
65143 endpoints = endpoints . Select ( e => new
66144 {
67145 method = e . Method ,
@@ -74,7 +152,9 @@ private void PrintApiInfo(OutputFormat outputFormat)
74152 else
75153 {
76154 _logger . LogInformation ( "Base URL: {BaseUrl}" , baseUrl ) ;
77- _logger . LogInformation ( "OpenAPI spec: {SwaggerUrl}" , $ "{ baseUrl } /swagger/v1/swagger.json") ;
155+ _logger . LogInformation ( "All endpoints require Authorization: Bearer <token>." ) ;
156+ _logger . LogInformation ( "Get your token: devproxy api token (use --pid <PID> when multiple instances are running)." ) ;
157+ _logger . LogInformation ( "Use devproxy status to discover running instances and their actual API ports." ) ;
78158 _logger . LogInformation ( "" ) ;
79159 _logger . LogInformation ( "Endpoints:" ) ;
80160 foreach ( var endpoint in endpoints )
@@ -90,4 +170,4 @@ sealed class ApiEndpointInfo
90170 public string Method { get ; set ; } = string . Empty ;
91171 public string Path { get ; set ; } = string . Empty ;
92172 public string Description { get ; set ; } = string . Empty ;
93- }
173+ }
0 commit comments