1
1
use anyhow:: { anyhow, Result } ;
2
2
use clap:: Args ;
3
3
use log:: { info, warn} ;
4
+ use reqwest:: Client ;
4
5
5
6
use crate :: ascii_art:: print_ascii_art;
7
+ use crate :: auth:: storage:: StoredAuth ;
8
+ use crate :: auth:: types:: { AuthTokens , UserInfo } ;
6
9
use crate :: auth:: { OidcClient , TokenStorage } ;
7
10
8
11
#[ derive( Args , Debug ) ]
@@ -13,6 +16,35 @@ pub struct Command {
13
16
/// Organization or user scope for publishing
14
17
#[ arg( long) ]
15
18
scope : Option < String > ,
19
+ /// API key for authentication (alternative to OAuth flow)
20
+ #[ arg( long) ]
21
+ api_key : Option < String > ,
22
+ }
23
+
24
+ async fn validate_api_key_and_get_user_info ( registry_url : & str , api_key : & str ) -> Result < UserInfo > {
25
+ let client = Client :: new ( ) ;
26
+ let user_info_url = format ! ( "{registry_url}/api/auth/oauth2/userinfo" ) ;
27
+
28
+ let response = client
29
+ . get ( & user_info_url)
30
+ . bearer_auth ( api_key)
31
+ . send ( )
32
+ . await
33
+ . map_err ( |e| anyhow ! ( "Failed to validate API key: {}" , e) ) ?;
34
+
35
+ if !response. status ( ) . is_success ( ) {
36
+ return Err ( anyhow ! (
37
+ "API key validation failed: HTTP {} - Please check your API key" ,
38
+ response. status( )
39
+ ) ) ;
40
+ }
41
+
42
+ let user_info: UserInfo = response
43
+ . json ( )
44
+ . await
45
+ . map_err ( |e| anyhow ! ( "Failed to parse user information: {}" , e) ) ?;
46
+
47
+ Ok ( user_info)
16
48
}
17
49
18
50
pub async fn handler ( args : & Command ) -> Result < ( ) > {
@@ -33,6 +65,59 @@ pub async fn handler(args: &Command) -> Result<()> {
33
65
34
66
info ! ( "Authenticating with registry: {registry_url}" ) ;
35
67
68
+ // Handle API key authentication
69
+ if let Some ( api_key) = & args. api_key {
70
+ info ! ( "Using API key authentication" ) ;
71
+
72
+ // Validate API key and get user info
73
+ let user_info = validate_api_key_and_get_user_info ( & registry_url, api_key) . await ?;
74
+
75
+ // Create auth tokens for API key
76
+ let auth_tokens = AuthTokens {
77
+ access_token : api_key. clone ( ) ,
78
+ refresh_token : None , // API keys don't have refresh tokens
79
+ expires_at : None , // API keys typically don't expire
80
+ scope : vec ! [
81
+ "read" . to_string( ) ,
82
+ "write" . to_string( ) ,
83
+ "publish" . to_string( ) ,
84
+ ] , // Default scopes
85
+ token_type : "Bearer" . to_string ( ) ,
86
+ } ;
87
+
88
+ // Create stored auth
89
+ let stored_auth = StoredAuth {
90
+ tokens : auth_tokens,
91
+ user : user_info. clone ( ) ,
92
+ registry : registry_url. clone ( ) ,
93
+ } ;
94
+
95
+ // Save authentication
96
+ storage. save_auth ( & stored_auth) ?;
97
+
98
+ println ! (
99
+ "✓ Successfully logged in with API key as {}" ,
100
+ user_info. username
101
+ ) ;
102
+
103
+ if let Some ( scope) = & args. scope {
104
+ info ! ( "Setting default publish scope to {scope}" ) ;
105
+ // TODO: Save scope preference to config
106
+ }
107
+
108
+ if let Some ( organizations) = & user_info. organizations {
109
+ println ! ( "This API key can only publish to the following organizations:" ) ;
110
+ for org in organizations {
111
+ println ! ( " - {} ({})" , org. name, org. role) ;
112
+ }
113
+ }
114
+
115
+ println ! ( "\n You can now publish packages using 'codemod publish'" ) ;
116
+ print_ascii_art ( ) ;
117
+ return Ok ( ( ) ) ;
118
+ }
119
+
120
+ // Continue with OIDC flow if no API key provided
36
121
let oidc_client = OidcClient :: new ( registry_url. clone ( ) , registry_config) ?;
37
122
38
123
// Check current auth status
0 commit comments