@@ -2,18 +2,18 @@ use crate::{
22 cmd:: create:: CreateCommon ,
33 config:: { Client , ClientType , Config } ,
44 http:: { HttpOptions , create_client} ,
5- oidc:: extra_scopes,
5+ oidc:: { extra_scopes, refresh_token_request } ,
66 server:: { Bind , Server } ,
77 utils:: OrNone ,
88} ;
99use anyhow:: { Context , bail} ;
1010use oauth2:: {
11- AuthorizationCode , ClientId , ClientSecret , CsrfToken , PkceCodeChallenge , RedirectUrl ,
12- TokenResponse ,
11+ AuthorizationCode , ClientId , ClientSecret , CsrfToken , EndpointMaybeSet , EndpointNotSet ,
12+ EndpointSet , PkceCodeChallenge , RedirectUrl , TokenResponse ,
1313} ;
1414use openidconnect:: {
1515 AuthenticationFlow , IssuerUrl , Nonce ,
16- core:: { CoreClient , CoreProviderMetadata , CoreResponseType } ,
16+ core:: { CoreClient , CoreProviderMetadata , CoreResponseType , CoreTokenResponse } ,
1717} ;
1818use std:: path:: PathBuf ;
1919
@@ -34,6 +34,10 @@ pub struct CreatePublic {
3434 #[ arg( short = 's' , long) ]
3535 pub client_secret : Option < String > ,
3636
37+ /// A refresh token to start with, instead of the authorization code flow
38+ #[ arg( short = 'R' , long) ]
39+ pub refresh_token : Option < String > ,
40+
3741 /// Force using a specific port for the local server
3842 #[ arg( short, long) ]
3943 pub port : Option < u16 > ,
@@ -58,6 +62,15 @@ pub struct CreatePublic {
5862 pub http : HttpOptions ,
5963}
6064
65+ type FlowClient = CoreClient <
66+ EndpointSet ,
67+ EndpointNotSet ,
68+ EndpointNotSet ,
69+ EndpointNotSet ,
70+ EndpointMaybeSet ,
71+ EndpointMaybeSet ,
72+ > ;
73+
6174impl CreatePublic {
6275 pub async fn run ( self ) -> anyhow:: Result < ( ) > {
6376 log:: debug!( "creating new client: {}" , self . common. name) ;
@@ -71,9 +84,6 @@ impl CreatePublic {
7184 ) ;
7285 }
7386
74- let server = Server :: new ( self . bind_mode ( ) , self . port ) . await ?;
75- let redirect = format ! ( "http://localhost:{}" , server. port) ;
76-
7787 let http = create_client ( & self . http ) . await ?;
7888
7989 let provider_metadata = CoreProviderMetadata :: discover_async (
@@ -86,8 +96,75 @@ impl CreatePublic {
8696 provider_metadata,
8797 ClientId :: new ( self . client_id . clone ( ) ) ,
8898 self . client_secret . clone ( ) . map ( ClientSecret :: new) ,
89- )
90- . set_redirect_uri ( RedirectUrl :: new ( redirect) ?) ;
99+ ) ;
100+
101+ let token = match self . refresh_token {
102+ None => self . code_flow ( & http, & client) . await ?,
103+ Some ( refresh_token) => {
104+ refresh_token_request ( & http, & client, self . common . scope . as_deref ( ) , refresh_token)
105+ . await ?
106+ }
107+ } ;
108+
109+ // log info
110+
111+ log:: info!( "First token:" ) ;
112+ log:: info!(
113+ " ID: {}" ,
114+ OrNone (
115+ & token
116+ . extra_fields( )
117+ . id_token( )
118+ . cloned( )
119+ . map( |t| t. to_string( ) )
120+ )
121+ ) ;
122+ log:: info!( " Access: {}" , token. access_token( ) . clone( ) . into_secret( ) ) ;
123+ log:: info!(
124+ " Refresh: {}" ,
125+ OrNone ( & token. refresh_token( ) . cloned( ) . map( |t| t. into_secret( ) ) )
126+ ) ;
127+
128+ // create client
129+
130+ let client = Client {
131+ issuer_url : self . common . issuer ,
132+ scope : self . common . scope ,
133+ r#type : ClientType :: Public {
134+ client_id : self . client_id ,
135+ client_secret : self . client_secret ,
136+ } ,
137+ state : Some ( token. into ( ) ) ,
138+ } ;
139+
140+ config
141+ . clients
142+ . insert ( self . common . name . clone ( ) , client. clone ( ) ) ;
143+
144+ config. store ( self . config . as_deref ( ) ) ?;
145+
146+ Ok ( ( ) )
147+ }
148+
149+ fn bind_mode ( & self ) -> Bind {
150+ if self . only4 {
151+ Bind :: Only4
152+ } else if self . only6 {
153+ Bind :: Only6
154+ } else {
155+ self . bind
156+ }
157+ }
158+
159+ async fn code_flow (
160+ & self ,
161+ http : & reqwest:: Client ,
162+ client : & FlowClient ,
163+ ) -> anyhow:: Result < CoreTokenResponse > {
164+ let server = Server :: new ( self . bind_mode ( ) , self . port ) . await ?;
165+ let redirect = format ! ( "http://localhost:{}" , server. port) ;
166+
167+ let client = client. clone ( ) . set_redirect_uri ( RedirectUrl :: new ( redirect) ?) ;
91168
92169 let ( pkce_challenge, pkce_verifier) = PkceCodeChallenge :: new_random_sha256 ( ) ;
93170
@@ -138,7 +215,7 @@ Open the following URL in your browser and perform the interactive login process
138215 let token = client
139216 . exchange_code ( AuthorizationCode :: new ( result. code ) ) ?
140217 . set_pkce_verifier ( pkce_verifier)
141- . request_async ( & http)
218+ . request_async ( http)
142219 . await ?;
143220
144221 // check ID token
@@ -150,53 +227,6 @@ Open the following URL in your browser and perform the interactive login process
150227 . context ( "failed to verify ID token" ) ?;
151228 }
152229
153- // log info
154-
155- log:: info!( "First token:" ) ;
156- log:: info!(
157- " ID: {}" ,
158- OrNone (
159- & token
160- . extra_fields( )
161- . id_token( )
162- . cloned( )
163- . map( |t| t. to_string( ) )
164- )
165- ) ;
166- log:: info!( " Access: {}" , token. access_token( ) . clone( ) . into_secret( ) ) ;
167- log:: info!(
168- " Refresh: {}" ,
169- OrNone ( & token. refresh_token( ) . cloned( ) . map( |t| t. into_secret( ) ) )
170- ) ;
171-
172- // create client
173-
174- let client = Client {
175- issuer_url : self . common . issuer ,
176- scope : self . common . scope ,
177- r#type : ClientType :: Public {
178- client_id : self . client_id ,
179- client_secret : self . client_secret ,
180- } ,
181- state : Some ( token. into ( ) ) ,
182- } ;
183-
184- config
185- . clients
186- . insert ( self . common . name . clone ( ) , client. clone ( ) ) ;
187-
188- config. store ( self . config . as_deref ( ) ) ?;
189-
190- Ok ( ( ) )
191- }
192-
193- fn bind_mode ( & self ) -> Bind {
194- if self . only4 {
195- Bind :: Only4
196- } else if self . only6 {
197- Bind :: Only6
198- } else {
199- self . bind
200- }
230+ Ok ( token)
201231 }
202232}
0 commit comments