Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/cli/src/auth.rs
3309 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
use crate::{
7
constants::{get_default_user_agent, APPLICATION_NAME, IS_INTERACTIVE_CLI, PRODUCT_NAME_LONG},
8
debug, error, info, log,
9
state::{LauncherPaths, PersistedState},
10
trace,
11
util::{
12
errors::{
13
wrap, AnyError, CodeError, OAuthError, RefreshTokenNotAvailableError, StatusError,
14
WrappedError,
15
},
16
input::prompt_options,
17
},
18
warning,
19
};
20
use async_trait::async_trait;
21
use chrono::{DateTime, Utc};
22
use gethostname::gethostname;
23
use serde::{de::DeserializeOwned, Deserialize, Serialize};
24
use std::{cell::Cell, fmt::Display, path::PathBuf, sync::Arc, thread};
25
use tokio::time::sleep;
26
use tunnels::{
27
contracts::PROD_FIRST_PARTY_APP_ID,
28
management::{Authorization, AuthorizationProvider, HttpError},
29
};
30
31
#[derive(Deserialize)]
32
struct DeviceCodeResponse {
33
device_code: String,
34
user_code: String,
35
message: Option<String>,
36
verification_uri: String,
37
expires_in: i64,
38
}
39
40
#[derive(Deserialize, Debug)]
41
struct AuthenticationResponse {
42
access_token: String,
43
refresh_token: Option<String>,
44
expires_in: Option<i64>,
45
}
46
47
#[derive(Deserialize)]
48
struct AuthenticationError {
49
error: String,
50
error_description: Option<String>,
51
}
52
53
#[derive(clap::ValueEnum, Serialize, Deserialize, Debug, Clone, Copy)]
54
pub enum AuthProvider {
55
Microsoft,
56
Github,
57
}
58
59
impl Display for AuthProvider {
60
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61
match self {
62
AuthProvider::Microsoft => write!(f, "Microsoft Account"),
63
AuthProvider::Github => write!(f, "GitHub Account"),
64
}
65
}
66
}
67
68
impl AuthProvider {
69
pub fn client_id(&self) -> &'static str {
70
match self {
71
AuthProvider::Microsoft => "aebc6443-996d-45c2-90f0-388ff96faa56",
72
AuthProvider::Github => "01ab8ac9400c4e429b23",
73
}
74
}
75
76
pub fn code_uri(&self) -> &'static str {
77
match self {
78
AuthProvider::Microsoft => {
79
"https://login.microsoftonline.com/organizations/oauth2/v2.0/devicecode"
80
}
81
AuthProvider::Github => "https://github.com/login/device/code",
82
}
83
}
84
85
pub fn grant_uri(&self) -> &'static str {
86
match self {
87
AuthProvider::Microsoft => {
88
"https://login.microsoftonline.com/organizations/oauth2/v2.0/token"
89
}
90
AuthProvider::Github => "https://github.com/login/oauth/access_token",
91
}
92
}
93
94
pub fn get_default_scopes(&self) -> String {
95
match self {
96
AuthProvider::Microsoft => {
97
format!("{PROD_FIRST_PARTY_APP_ID}/.default+offline_access+profile+openid")
98
}
99
AuthProvider::Github => "read:user+read:org".to_string(),
100
}
101
}
102
}
103
104
#[derive(Serialize, Deserialize, Debug, Clone)]
105
pub struct StoredCredential {
106
#[serde(rename = "p")]
107
pub(crate) provider: AuthProvider,
108
#[serde(rename = "a")]
109
access_token: String,
110
#[serde(rename = "r")]
111
refresh_token: Option<String>,
112
#[serde(rename = "e")]
113
expires_at: Option<DateTime<Utc>>,
114
}
115
116
const GH_USER_ENDPOINT: &str = "https://api.github.com/user";
117
118
async fn get_github_user(
119
client: &reqwest::Client,
120
access_token: &str,
121
) -> Result<reqwest::Response, reqwest::Error> {
122
client
123
.get(GH_USER_ENDPOINT)
124
.header("Authorization", format!("token {access_token}"))
125
.header("User-Agent", get_default_user_agent())
126
.send()
127
.await
128
}
129
130
impl StoredCredential {
131
pub async fn is_expired(&self, log: &log::Logger, client: &reqwest::Client) -> bool {
132
match self.provider {
133
AuthProvider::Microsoft => self
134
.expires_at
135
.map(|e| Utc::now() + chrono::Duration::minutes(5) > e)
136
.unwrap_or(false),
137
138
// Make an auth request to Github. Mark the credential as expired
139
// only on a verifiable 4xx code. We don't error on any failed
140
// request since then a drop in connection could "require" a refresh
141
AuthProvider::Github => {
142
let res = get_github_user(client, &self.access_token).await;
143
let res = match res {
144
Ok(r) => r,
145
Err(e) => {
146
warning!(log, "failed to check GitHub token: {}", e);
147
return false;
148
}
149
};
150
151
if res.status().is_success() {
152
return false;
153
}
154
155
let err = StatusError::from_res(res).await;
156
debug!(log, "GitHub token looks expired: {:?}", err);
157
true
158
}
159
}
160
}
161
162
fn from_response(auth: AuthenticationResponse, provider: AuthProvider) -> Self {
163
StoredCredential {
164
provider,
165
access_token: auth.access_token,
166
refresh_token: auth.refresh_token,
167
expires_at: auth
168
.expires_in
169
.map(|e| Utc::now() + chrono::Duration::seconds(e)),
170
}
171
}
172
}
173
174
struct StorageWithLastRead {
175
storage: Box<dyn StorageImplementation>,
176
fallback_storage: Option<FileStorage>,
177
last_read: Cell<Result<Option<StoredCredential>, WrappedError>>,
178
}
179
180
#[derive(Clone)]
181
pub struct Auth {
182
client: reqwest::Client,
183
log: log::Logger,
184
file_storage_path: PathBuf,
185
storage: Arc<std::sync::Mutex<Option<StorageWithLastRead>>>,
186
}
187
188
trait StorageImplementation: Send + Sync {
189
fn read(&mut self) -> Result<Option<StoredCredential>, AnyError>;
190
fn store(&mut self, value: StoredCredential) -> Result<(), AnyError>;
191
fn clear(&mut self) -> Result<(), AnyError>;
192
}
193
194
// unseal decrypts and deserializes the value
195
fn seal<T>(value: &T) -> String
196
where
197
T: Serialize + ?Sized,
198
{
199
let dec = serde_json::to_string(value).expect("expected to serialize");
200
if std::env::var("VSCODE_CLI_DISABLE_KEYCHAIN_ENCRYPT").is_ok() {
201
return dec;
202
}
203
encrypt(&dec)
204
}
205
206
// unseal decrypts and deserializes the value
207
fn unseal<T>(value: &str) -> Option<T>
208
where
209
T: DeserializeOwned,
210
{
211
// small back-compat for old unencrypted values, or if VSCODE_CLI_DISABLE_KEYCHAIN_ENCRYPT set
212
if let Ok(v) = serde_json::from_str::<T>(value) {
213
return Some(v);
214
}
215
216
let dec = decrypt(value)?;
217
serde_json::from_str::<T>(&dec).ok()
218
}
219
220
#[cfg(target_os = "windows")]
221
const KEYCHAIN_ENTRY_LIMIT: usize = 1024;
222
#[cfg(not(target_os = "windows"))]
223
const KEYCHAIN_ENTRY_LIMIT: usize = 128 * 1024;
224
225
const CONTINUE_MARKER: &str = "<MORE>";
226
227
/// Implementation that wraps the KeyringStorage on Linux to avoid
228
/// https://github.com/hwchen/keyring-rs/issues/132
229
struct ThreadKeyringStorage {
230
s: Option<KeyringStorage>,
231
}
232
233
impl ThreadKeyringStorage {
234
fn thread_op<R, Fn>(&mut self, f: Fn) -> Result<R, AnyError>
235
where
236
Fn: 'static + Send + FnOnce(&mut KeyringStorage) -> Result<R, AnyError>,
237
R: 'static + Send,
238
{
239
let mut s = match self.s.take() {
240
Some(s) => s,
241
None => return Err(CodeError::KeyringTimeout.into()),
242
};
243
244
// It seems like on Linux communication to the keyring can block indefinitely.
245
// Fall back after a 5 second timeout.
246
let (sender, receiver) = std::sync::mpsc::channel();
247
let tsender = sender.clone();
248
249
thread::spawn(move || sender.send(Some((f(&mut s), s))));
250
thread::spawn(move || {
251
thread::sleep(std::time::Duration::from_secs(5));
252
let _ = tsender.send(None);
253
});
254
255
match receiver.recv().unwrap() {
256
Some((r, s)) => {
257
self.s = Some(s);
258
r
259
}
260
None => Err(CodeError::KeyringTimeout.into()),
261
}
262
}
263
}
264
265
impl Default for ThreadKeyringStorage {
266
fn default() -> Self {
267
Self {
268
s: Some(KeyringStorage::default()),
269
}
270
}
271
}
272
273
impl StorageImplementation for ThreadKeyringStorage {
274
fn read(&mut self) -> Result<Option<StoredCredential>, AnyError> {
275
self.thread_op(|s| s.read())
276
}
277
278
fn store(&mut self, value: StoredCredential) -> Result<(), AnyError> {
279
self.thread_op(move |s| s.store(value))
280
}
281
282
fn clear(&mut self) -> Result<(), AnyError> {
283
self.thread_op(|s| s.clear())
284
}
285
}
286
287
#[derive(Default)]
288
struct KeyringStorage {
289
// keyring storage can be split into multiple entries due to entry length limits
290
// on Windows https://github.com/microsoft/vscode-cli/issues/358
291
entries: Vec<keyring::Entry>,
292
}
293
294
macro_rules! get_next_entry {
295
($self: expr, $i: expr) => {
296
match $self.entries.get($i) {
297
Some(e) => e,
298
None => {
299
let e = keyring::Entry::new("vscode-cli", &format!("vscode-cli-{}", $i)).unwrap();
300
$self.entries.push(e);
301
$self.entries.last().unwrap()
302
}
303
}
304
};
305
}
306
307
impl StorageImplementation for KeyringStorage {
308
fn read(&mut self) -> Result<Option<StoredCredential>, AnyError> {
309
let mut str = String::new();
310
311
for i in 0.. {
312
let entry = get_next_entry!(self, i);
313
let next_chunk = match entry.get_password() {
314
Ok(value) => value,
315
Err(keyring::Error::NoEntry) => return Ok(None), // missing entries?
316
Err(e) => return Err(wrap(e, "error reading keyring").into()),
317
};
318
319
if next_chunk.ends_with(CONTINUE_MARKER) {
320
str.push_str(&next_chunk[..next_chunk.len() - CONTINUE_MARKER.len()]);
321
} else {
322
str.push_str(&next_chunk);
323
break;
324
}
325
}
326
327
Ok(unseal(&str))
328
}
329
330
fn store(&mut self, value: StoredCredential) -> Result<(), AnyError> {
331
let sealed = seal(&value);
332
let step_size = KEYCHAIN_ENTRY_LIMIT - CONTINUE_MARKER.len();
333
334
for i in (0..sealed.len()).step_by(step_size) {
335
let entry = get_next_entry!(self, i / step_size);
336
337
let cutoff = i + step_size;
338
let stored = if cutoff <= sealed.len() {
339
let mut part = sealed[i..cutoff].to_string();
340
part.push_str(CONTINUE_MARKER);
341
entry.set_password(&part)
342
} else {
343
entry.set_password(&sealed[i..])
344
};
345
346
if let Err(e) = stored {
347
return Err(wrap(e, "error updating keyring").into());
348
}
349
}
350
351
Ok(())
352
}
353
354
fn clear(&mut self) -> Result<(), AnyError> {
355
self.read().ok(); // make sure component parts are available
356
for entry in self.entries.iter() {
357
entry
358
.delete_password()
359
.map_err(|e| wrap(e, "error updating keyring"))?;
360
}
361
self.entries.clear();
362
363
Ok(())
364
}
365
}
366
367
struct FileStorage(PersistedState<Option<String>>);
368
369
impl StorageImplementation for FileStorage {
370
fn read(&mut self) -> Result<Option<StoredCredential>, AnyError> {
371
Ok(self.0.load().and_then(|s| unseal(&s)))
372
}
373
374
fn store(&mut self, value: StoredCredential) -> Result<(), AnyError> {
375
self.0.save(Some(seal(&value))).map_err(|e| e.into())
376
}
377
378
fn clear(&mut self) -> Result<(), AnyError> {
379
self.0.save(None).map_err(|e| e.into())
380
}
381
}
382
383
impl Auth {
384
pub fn new(paths: &LauncherPaths, log: log::Logger) -> Auth {
385
Auth {
386
log,
387
client: reqwest::Client::new(),
388
file_storage_path: paths.root().join("token.json"),
389
storage: Arc::new(std::sync::Mutex::new(None)),
390
}
391
}
392
393
fn with_storage<T, F>(&self, op: F) -> T
394
where
395
F: FnOnce(&mut StorageWithLastRead) -> T,
396
{
397
let mut opt = self.storage.lock().unwrap();
398
if let Some(s) = opt.as_mut() {
399
return op(s);
400
}
401
402
#[cfg(not(target_os = "linux"))]
403
let mut keyring_storage = KeyringStorage::default();
404
#[cfg(target_os = "linux")]
405
let mut keyring_storage = ThreadKeyringStorage::default();
406
let mut file_storage = FileStorage(PersistedState::new_with_mode(
407
self.file_storage_path.clone(),
408
0o600,
409
));
410
411
let native_storage_result = if std::env::var("VSCODE_CLI_USE_FILE_KEYCHAIN").is_ok()
412
|| self.file_storage_path.exists()
413
{
414
Err(wrap("", "user prefers file storage").into())
415
} else {
416
keyring_storage.read()
417
};
418
419
let mut storage = match native_storage_result {
420
Ok(v) => StorageWithLastRead {
421
last_read: Cell::new(Ok(v)),
422
fallback_storage: Some(file_storage),
423
storage: Box::new(keyring_storage),
424
},
425
Err(e) => {
426
debug!(self.log, "Using file keychain storage due to: {}", e);
427
StorageWithLastRead {
428
last_read: Cell::new(
429
file_storage
430
.read()
431
.map_err(|e| wrap(e, "could not read from file storage")),
432
),
433
fallback_storage: None,
434
storage: Box::new(file_storage),
435
}
436
}
437
};
438
439
let out = op(&mut storage);
440
*opt = Some(storage);
441
out
442
}
443
444
/// Gets a tunnel Authentication for use in the tunnel management API.
445
pub async fn get_tunnel_authentication(&self) -> Result<Authorization, AnyError> {
446
let cred = self.get_credential().await?;
447
let auth = match cred.provider {
448
AuthProvider::Microsoft => Authorization::Bearer(cred.access_token),
449
AuthProvider::Github => Authorization::Github(format!(
450
"client_id={} {}",
451
cred.provider.client_id(),
452
cred.access_token
453
)),
454
};
455
456
Ok(auth)
457
}
458
459
/// Reads the current details from the keyring.
460
pub fn get_current_credential(&self) -> Result<Option<StoredCredential>, WrappedError> {
461
self.with_storage(|storage| {
462
let value = storage.last_read.replace(Ok(None));
463
storage.last_read.set(value.clone());
464
value
465
})
466
}
467
468
/// Clears login info from the keyring.
469
pub fn clear_credentials(&self) -> Result<(), AnyError> {
470
self.with_storage(|storage| {
471
storage.storage.clear()?;
472
storage.last_read.set(Ok(None));
473
Ok(())
474
})
475
}
476
477
/// Runs the login flow, optionally pre-filling a provider and/or access token.
478
pub async fn login(
479
&self,
480
provider: Option<AuthProvider>,
481
access_token: Option<String>,
482
refresh_token: Option<String>,
483
) -> Result<StoredCredential, AnyError> {
484
let provider = match provider {
485
Some(p) => p,
486
None => self.prompt_for_provider().await?,
487
};
488
489
let credentials = match access_token {
490
Some(t) => StoredCredential {
491
provider,
492
access_token: t,
493
// if a refresh token is given, assume it's valid now but refresh it
494
// soon in order to get the real expiry time.
495
expires_at: refresh_token
496
.as_ref()
497
.map(|_| Utc::now() + chrono::Duration::minutes(5)),
498
refresh_token,
499
},
500
None => self.do_device_code_flow_with_provider(provider).await?,
501
};
502
503
self.store_credentials(credentials.clone());
504
Ok(credentials)
505
}
506
507
/// Gets the currently stored credentials, or asks the user to log in.
508
pub async fn get_credential(&self) -> Result<StoredCredential, AnyError> {
509
let entry = match self.get_current_credential() {
510
Ok(Some(old_creds)) => {
511
trace!(self.log, "Found token in keyring");
512
match self.maybe_refresh_token(&old_creds).await {
513
Ok(Some(new_creds)) => {
514
self.store_credentials(new_creds.clone());
515
new_creds
516
}
517
Ok(None) => old_creds,
518
Err(e) => {
519
info!(self.log, "error refreshing token: {}", e);
520
let new_creds = self
521
.do_device_code_flow_with_provider(old_creds.provider)
522
.await?;
523
self.store_credentials(new_creds.clone());
524
new_creds
525
}
526
}
527
}
528
529
Ok(None) => {
530
trace!(self.log, "No token in keyring, getting a new one");
531
let creds = self.do_device_code_flow().await?;
532
self.store_credentials(creds.clone());
533
creds
534
}
535
536
Err(e) => {
537
warning!(
538
self.log,
539
"Error reading token from keyring, getting a new one: {}",
540
e
541
);
542
let creds = self.do_device_code_flow().await?;
543
self.store_credentials(creds.clone());
544
creds
545
}
546
};
547
548
Ok(entry)
549
}
550
551
/// Stores credentials, logging a warning if it fails.
552
fn store_credentials(&self, creds: StoredCredential) {
553
self.with_storage(|storage| {
554
if let Err(e) = storage.storage.store(creds.clone()) {
555
warning!(
556
self.log,
557
"Failed to update keyring with new credentials: {}",
558
e
559
);
560
561
if let Some(fb) = storage.fallback_storage.take() {
562
storage.storage = Box::new(fb);
563
match storage.storage.store(creds.clone()) {
564
Err(e) => {
565
warning!(self.log, "Also failed to update fallback storage: {}", e)
566
}
567
Ok(_) => debug!(self.log, "Updated fallback storage successfully"),
568
}
569
}
570
}
571
572
storage.last_read.set(Ok(Some(creds)));
573
})
574
}
575
576
/// Refreshes the token in the credentials if necessary. Returns None if
577
/// the token is up to date, or Some new token otherwise.
578
async fn maybe_refresh_token(
579
&self,
580
creds: &StoredCredential,
581
) -> Result<Option<StoredCredential>, AnyError> {
582
if !creds.is_expired(&self.log, &self.client).await {
583
return Ok(None);
584
}
585
586
self.do_refresh_token(creds).await
587
}
588
589
/// Refreshes the token in the credentials. Returns an error if the process failed.
590
/// Returns None if the token didn't change.
591
async fn do_refresh_token(
592
&self,
593
creds: &StoredCredential,
594
) -> Result<Option<StoredCredential>, AnyError> {
595
match &creds.refresh_token {
596
Some(t) => self
597
.do_grant(
598
creds.provider,
599
format!(
600
"client_id={}&grant_type=refresh_token&refresh_token={}",
601
creds.provider.client_id(),
602
t
603
),
604
)
605
.await
606
.map(Some),
607
None => match creds.provider {
608
AuthProvider::Github => self.touch_github_token(creds).await.map(|_| None),
609
_ => Err(RefreshTokenNotAvailableError().into()),
610
},
611
}
612
}
613
614
/// Does a "grant token" request.
615
async fn do_grant(
616
&self,
617
provider: AuthProvider,
618
body: String,
619
) -> Result<StoredCredential, AnyError> {
620
let response = self
621
.client
622
.post(provider.grant_uri())
623
.body(body)
624
.header("Accept", "application/json")
625
.send()
626
.await?;
627
628
let status_code = response.status().as_u16();
629
let body = response.bytes().await?;
630
if let Ok(body) = serde_json::from_slice::<AuthenticationResponse>(&body) {
631
return Ok(StoredCredential::from_response(body, provider));
632
}
633
634
Err(Auth::handle_grant_error(
635
provider.grant_uri(),
636
status_code,
637
body,
638
))
639
}
640
641
/// GH doesn't have a refresh token, but does limit to the 10 most recently
642
/// used tokens per user (#9052), so for the github "refresh" just request
643
/// the current user.
644
async fn touch_github_token(&self, credential: &StoredCredential) -> Result<(), AnyError> {
645
let response = get_github_user(&self.client, &credential.access_token).await?;
646
if response.status().is_success() {
647
return Ok(());
648
}
649
650
let status_code = response.status().as_u16();
651
let body = response.bytes().await?;
652
Err(Auth::handle_grant_error(
653
GH_USER_ENDPOINT,
654
status_code,
655
body,
656
))
657
}
658
659
fn handle_grant_error(url: &str, status_code: u16, body: bytes::Bytes) -> AnyError {
660
if let Ok(res) = serde_json::from_slice::<AuthenticationError>(&body) {
661
return OAuthError {
662
error: res.error,
663
error_description: res.error_description,
664
}
665
.into();
666
}
667
668
StatusError {
669
body: String::from_utf8_lossy(&body).to_string(),
670
status_code,
671
url: url.to_string(),
672
}
673
.into()
674
}
675
/// Implements the device code flow, returning the credentials upon success.
676
async fn do_device_code_flow(&self) -> Result<StoredCredential, AnyError> {
677
let provider = self.prompt_for_provider().await?;
678
self.do_device_code_flow_with_provider(provider).await
679
}
680
681
async fn prompt_for_provider(&self) -> Result<AuthProvider, AnyError> {
682
if !*IS_INTERACTIVE_CLI {
683
info!(
684
self.log,
685
"Using GitHub for authentication, run `{} tunnel user login --provider <provider>` option to change this.",
686
APPLICATION_NAME
687
);
688
return Ok(AuthProvider::Github);
689
}
690
691
let provider = prompt_options(
692
format!("How would you like to log in to {PRODUCT_NAME_LONG}?"),
693
&[AuthProvider::Microsoft, AuthProvider::Github],
694
)?;
695
696
Ok(provider)
697
}
698
699
async fn do_device_code_flow_with_provider(
700
&self,
701
provider: AuthProvider,
702
) -> Result<StoredCredential, AnyError> {
703
loop {
704
let init_code = self
705
.client
706
.post(provider.code_uri())
707
.header("Accept", "application/json")
708
.body(format!(
709
"client_id={}&scope={}",
710
provider.client_id(),
711
provider.get_default_scopes(),
712
))
713
.send()
714
.await?;
715
716
if !init_code.status().is_success() {
717
return Err(StatusError::from_res(init_code).await?.into());
718
}
719
720
let init_code_json = init_code.json::<DeviceCodeResponse>().await?;
721
let expires_at = Utc::now() + chrono::Duration::seconds(init_code_json.expires_in);
722
723
match &init_code_json.message {
724
Some(m) => self.log.result(m),
725
None => self.log.result(format!(
726
"To grant access to the server, please log into {} and use code {}",
727
init_code_json.verification_uri, init_code_json.user_code
728
)),
729
};
730
731
let body = format!(
732
"client_id={}&grant_type=urn:ietf:params:oauth:grant-type:device_code&device_code={}",
733
provider.client_id(),
734
init_code_json.device_code
735
);
736
737
let mut interval_s = 5;
738
while Utc::now() < expires_at {
739
sleep(std::time::Duration::from_secs(interval_s)).await;
740
741
match self.do_grant(provider, body.clone()).await {
742
Ok(creds) => return Ok(creds),
743
Err(AnyError::OAuthError(e)) if e.error == "slow_down" => {
744
interval_s += 5; // https://www.rfc-editor.org/rfc/rfc8628#section-3.5
745
trace!(self.log, "refresh poll failed, slowing down");
746
}
747
// Github returns a non-standard 429 to slow down
748
Err(AnyError::StatusError(e)) if e.status_code == 429 => {
749
interval_s += 5; // https://www.rfc-editor.org/rfc/rfc8628#section-3.5
750
trace!(self.log, "refresh poll failed, slowing down");
751
}
752
Err(e) => {
753
trace!(self.log, "refresh poll failed, retrying: {}", e);
754
}
755
}
756
}
757
}
758
}
759
760
/// Maintains the stored credential by refreshing it against the service
761
/// to ensure its stays current. Returns a future that should be polled and
762
/// only errors if a refresh fails in a consistent way.
763
pub async fn keep_token_alive(self) -> Result<(), AnyError> {
764
let this = self.clone();
765
let default_refresh = std::time::Duration::from_secs(60 * 60);
766
let min_refresh = std::time::Duration::from_secs(10);
767
768
let mut credential = this.get_credential().await?;
769
let mut last_did_error = false;
770
loop {
771
let sleep_time = if last_did_error {
772
min_refresh
773
} else {
774
match credential.expires_at {
775
Some(d) => ((d - Utc::now()) * 2 / 3).to_std().unwrap_or(min_refresh),
776
None => default_refresh,
777
}
778
};
779
780
// to_std errors on negative duration, fall back to a 60s refresh
781
tokio::time::sleep(sleep_time.max(min_refresh)).await;
782
783
match this.do_refresh_token(&credential).await {
784
// 4xx error means this token is probably not good any mode
785
Err(AnyError::StatusError(e)) if e.status_code >= 400 && e.status_code < 500 => {
786
error!(this.log, "failed to keep token alive: {:?}", e);
787
return Err(e.into());
788
}
789
Err(AnyError::RefreshTokenNotAvailableError(_)) => {
790
return Ok(());
791
}
792
Err(e) => {
793
warning!(this.log, "error refreshing token: {:?}", e);
794
last_did_error = true;
795
continue;
796
}
797
Ok(c) => {
798
trace!(this.log, "token was successfully refreshed in keepalive");
799
last_did_error = false;
800
if let Some(c) = c {
801
this.store_credentials(c.clone());
802
credential = c;
803
}
804
}
805
}
806
}
807
}
808
}
809
810
#[async_trait]
811
impl AuthorizationProvider for Auth {
812
async fn get_authorization(&self) -> Result<Authorization, HttpError> {
813
self.get_tunnel_authentication()
814
.await
815
.map_err(|e| HttpError::AuthorizationError(e.to_string()))
816
}
817
}
818
819
lazy_static::lazy_static! {
820
static ref HOSTNAME: Vec<u8> = gethostname().to_string_lossy().bytes().collect();
821
}
822
823
#[cfg(feature = "vscode-encrypt")]
824
fn encrypt(value: &str) -> String {
825
vscode_encrypt::encrypt(&HOSTNAME, value.as_bytes()).expect("expected to encrypt")
826
}
827
828
#[cfg(feature = "vscode-encrypt")]
829
fn decrypt(value: &str) -> Option<String> {
830
let b = vscode_encrypt::decrypt(&HOSTNAME, value).ok()?;
831
String::from_utf8(b).ok()
832
}
833
834
#[cfg(not(feature = "vscode-encrypt"))]
835
fn encrypt(value: &str) -> String {
836
value.to_owned()
837
}
838
839
#[cfg(not(feature = "vscode-encrypt"))]
840
fn decrypt(value: &str) -> Option<String> {
841
Some(value.to_owned())
842
}
843
844