LCOV - code coverage report
Current view: top level - proxy/src/auth/backend - link.rs (source / functions) Coverage Total Hit
Test: 32f4a56327bc9da697706839ed4836b2a00a408f.info Lines: 83.8 % 74 62
Test Date: 2024-02-07 07:37:29 Functions: 28.6 % 21 6

            Line data    Source code
       1              : use crate::{
       2              :     auth, compute,
       3              :     console::{self, provider::NodeInfo},
       4              :     context::RequestMonitoring,
       5              :     error::UserFacingError,
       6              :     stream::PqStream,
       7              :     waiters,
       8              : };
       9              : use pq_proto::BeMessage as Be;
      10              : use thiserror::Error;
      11              : use tokio::io::{AsyncRead, AsyncWrite};
      12              : use tokio_postgres::config::SslMode;
      13              : use tracing::{info, info_span};
      14              : 
      15            0 : #[derive(Debug, Error)]
      16              : pub enum LinkAuthError {
      17              :     /// Authentication error reported by the console.
      18              :     #[error("Authentication failed: {0}")]
      19              :     AuthFailed(String),
      20              : 
      21              :     #[error(transparent)]
      22              :     WaiterRegister(#[from] waiters::RegisterError),
      23              : 
      24              :     #[error(transparent)]
      25              :     WaiterWait(#[from] waiters::WaitError),
      26              : 
      27              :     #[error(transparent)]
      28              :     Io(#[from] std::io::Error),
      29              : }
      30              : 
      31              : impl UserFacingError for LinkAuthError {
      32            0 :     fn to_string_client(&self) -> String {
      33            0 :         use LinkAuthError::*;
      34            0 :         match self {
      35            0 :             AuthFailed(_) => self.to_string(),
      36            0 :             _ => "Internal error".to_string(),
      37              :         }
      38            0 :     }
      39              : }
      40              : 
      41            3 : fn hello_message(redirect_uri: &reqwest::Url, session_id: &str) -> String {
      42            3 :     format!(
      43            3 :         concat![
      44            3 :             "Welcome to Neon!\n",
      45            3 :             "Authenticate by visiting:\n",
      46            3 :             "    {redirect_uri}{session_id}\n\n",
      47            3 :         ],
      48            3 :         redirect_uri = redirect_uri,
      49            3 :         session_id = session_id,
      50            3 :     )
      51            3 : }
      52              : 
      53            3 : pub fn new_psql_session_id() -> String {
      54            3 :     hex::encode(rand::random::<[u8; 8]>())
      55            3 : }
      56              : 
      57            3 : pub(super) async fn authenticate(
      58            3 :     ctx: &mut RequestMonitoring,
      59            3 :     link_uri: &reqwest::Url,
      60            3 :     client: &mut PqStream<impl AsyncRead + AsyncWrite + Unpin>,
      61            3 : ) -> auth::Result<NodeInfo> {
      62              :     // registering waiter can fail if we get unlucky with rng.
      63              :     // just try again.
      64            3 :     let (psql_session_id, waiter) = loop {
      65            3 :         let psql_session_id = new_psql_session_id();
      66            3 : 
      67            3 :         match console::mgmt::get_waiter(&psql_session_id) {
      68            3 :             Ok(waiter) => break (psql_session_id, waiter),
      69            0 :             Err(_e) => continue,
      70              :         }
      71              :     };
      72              : 
      73            3 :     let span = info_span!("link", psql_session_id = &psql_session_id);
      74            3 :     let greeting = hello_message(link_uri, &psql_session_id);
      75            3 : 
      76            3 :     // Give user a URL to spawn a new database.
      77            3 :     info!(parent: &span, "sending the auth URL to the user");
      78            3 :     client
      79            3 :         .write_message_noflush(&Be::AuthenticationOk)?
      80            3 :         .write_message_noflush(&Be::CLIENT_ENCODING)?
      81            3 :         .write_message(&Be::NoticeResponse(&greeting))
      82            0 :         .await?;
      83              : 
      84              :     // Wait for web console response (see `mgmt`).
      85            3 :     info!(parent: &span, "waiting for console's reply...");
      86            3 :     let db_info = waiter.await.map_err(LinkAuthError::from)?;
      87              : 
      88            3 :     client.write_message_noflush(&Be::NoticeResponse("Connecting to database."))?;
      89              : 
      90              :     // This config should be self-contained, because we won't
      91              :     // take username or dbname from client's startup message.
      92            3 :     let mut config = compute::ConnCfg::new();
      93            3 :     config
      94            3 :         .host(&db_info.host)
      95            3 :         .port(db_info.port)
      96            3 :         .dbname(&db_info.dbname)
      97            3 :         .user(&db_info.user);
      98            3 : 
      99            3 :     ctx.set_user(db_info.user.into());
     100            3 :     ctx.set_project(db_info.aux.clone());
     101            3 :     tracing::Span::current().record("ep", &tracing::field::display(&db_info.aux.endpoint_id));
     102            3 : 
     103            3 :     // Backwards compatibility. pg_sni_proxy uses "--" in domain names
     104            3 :     // while direct connections do not. Once we migrate to pg_sni_proxy
     105            3 :     // everywhere, we can remove this.
     106            3 :     if db_info.host.contains("--") {
     107            0 :         // we need TLS connection with SNI info to properly route it
     108            0 :         config.ssl_mode(SslMode::Require);
     109            3 :     } else {
     110            3 :         config.ssl_mode(SslMode::Disable);
     111            3 :     }
     112              : 
     113            3 :     if let Some(password) = db_info.password {
     114            0 :         config.password(password.as_ref());
     115            3 :     }
     116              : 
     117            3 :     Ok(NodeInfo {
     118            3 :         config,
     119            3 :         aux: db_info.aux,
     120            3 :         allow_self_signed_compute: false, // caller may override
     121            3 :     })
     122            3 : }
        

Generated by: LCOV version 2.1-beta