mirror of
https://github.com/warp-tech/warpgate.git
synced 2024-09-20 06:46:17 +08:00
fixed #854 - show session details during OOB auth
This commit is contained in:
parent
fc1a93b9e3
commit
0bc9ae1b1a
24
Cargo.lock
generated
24
Cargo.lock
generated
|
@ -4976,7 +4976,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "warpgate"
|
||||
version = "0.7.3"
|
||||
version = "0.7.4"
|
||||
dependencies = [
|
||||
"ansi_term",
|
||||
"anyhow",
|
||||
|
@ -5012,7 +5012,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "warpgate-admin"
|
||||
version = "0.7.3"
|
||||
version = "0.7.4"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
|
@ -5041,7 +5041,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "warpgate-common"
|
||||
version = "0.7.3"
|
||||
version = "0.7.4"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argon2",
|
||||
|
@ -5077,7 +5077,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "warpgate-core"
|
||||
version = "0.7.3"
|
||||
version = "0.7.4"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argon2",
|
||||
|
@ -5117,7 +5117,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "warpgate-database-protocols"
|
||||
version = "0.7.3"
|
||||
version = "0.7.4"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bytes",
|
||||
|
@ -5130,7 +5130,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "warpgate-db-entities"
|
||||
version = "0.7.3"
|
||||
version = "0.7.4"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"poem-openapi",
|
||||
|
@ -5143,7 +5143,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "warpgate-db-migrations"
|
||||
version = "0.7.3"
|
||||
version = "0.7.4"
|
||||
dependencies = [
|
||||
"async-std",
|
||||
"chrono",
|
||||
|
@ -5155,7 +5155,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "warpgate-protocol-http"
|
||||
version = "0.7.3"
|
||||
version = "0.7.4"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
|
@ -5187,7 +5187,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "warpgate-protocol-mysql"
|
||||
version = "0.7.3"
|
||||
version = "0.7.4"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
|
@ -5214,7 +5214,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "warpgate-protocol-ssh"
|
||||
version = "0.7.3"
|
||||
version = "0.7.4"
|
||||
dependencies = [
|
||||
"ansi_term",
|
||||
"anyhow",
|
||||
|
@ -5239,7 +5239,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "warpgate-sso"
|
||||
version = "0.7.3"
|
||||
version = "0.7.4"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"data-encoding",
|
||||
|
@ -5255,7 +5255,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "warpgate-web"
|
||||
version = "0.7.3"
|
||||
version = "0.7.4"
|
||||
dependencies = [
|
||||
"rust-embed",
|
||||
"serde",
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
use std::collections::HashSet;
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use rand::Rng;
|
||||
use uuid::Uuid;
|
||||
|
||||
use super::{AuthCredential, CredentialKind, CredentialPolicy, CredentialPolicyResponse};
|
||||
use crate::SessionId;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum AuthResult {
|
||||
|
@ -13,27 +16,43 @@ pub enum AuthResult {
|
|||
|
||||
pub struct AuthState {
|
||||
id: Uuid,
|
||||
session_id: Option<Uuid>,
|
||||
username: String,
|
||||
protocol: String,
|
||||
force_rejected: bool,
|
||||
policy: Box<dyn CredentialPolicy + Sync + Send>,
|
||||
valid_credentials: Vec<AuthCredential>,
|
||||
started: DateTime<Utc>,
|
||||
identification_string: String,
|
||||
}
|
||||
|
||||
fn generate_identification_string() -> String {
|
||||
let mut s = String::new();
|
||||
let mut rng = rand::thread_rng();
|
||||
for _ in 0..4 {
|
||||
s.push_str(&format!("{:X}", rng.gen_range(0..16)));
|
||||
}
|
||||
s
|
||||
}
|
||||
|
||||
impl AuthState {
|
||||
pub fn new(
|
||||
id: Uuid,
|
||||
session_id: Option<SessionId>,
|
||||
username: String,
|
||||
protocol: String,
|
||||
policy: Box<dyn CredentialPolicy + Sync + Send>,
|
||||
) -> Self {
|
||||
Self {
|
||||
id,
|
||||
session_id,
|
||||
username,
|
||||
protocol,
|
||||
force_rejected: false,
|
||||
policy,
|
||||
valid_credentials: vec![],
|
||||
started: Utc::now(),
|
||||
identification_string: generate_identification_string(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -41,6 +60,10 @@ impl AuthState {
|
|||
&self.id
|
||||
}
|
||||
|
||||
pub fn session_id(&self) -> &Option<SessionId> {
|
||||
&self.session_id
|
||||
}
|
||||
|
||||
pub fn username(&self) -> &str {
|
||||
&self.username
|
||||
}
|
||||
|
@ -49,6 +72,14 @@ impl AuthState {
|
|||
&self.protocol
|
||||
}
|
||||
|
||||
pub fn started(&self) -> &DateTime<Utc> {
|
||||
&self.started
|
||||
}
|
||||
|
||||
pub fn identification_string(&self) -> &str {
|
||||
&self.identification_string
|
||||
}
|
||||
|
||||
pub fn add_valid_credential(&mut self, credential: AuthCredential) {
|
||||
self.valid_credentials.push(credential);
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ use once_cell::sync::Lazy;
|
|||
use tokio::sync::{broadcast, Mutex};
|
||||
use uuid::Uuid;
|
||||
use warpgate_common::auth::{AuthResult, AuthState};
|
||||
use warpgate_common::WarpgateError;
|
||||
use warpgate_common::{WarpgateError, SessionId};
|
||||
|
||||
use crate::ConfigProvider;
|
||||
|
||||
|
@ -49,6 +49,7 @@ impl AuthStateStore {
|
|||
|
||||
pub async fn create(
|
||||
&mut self,
|
||||
session_id: Option<&SessionId>,
|
||||
username: &str,
|
||||
protocol: &str,
|
||||
) -> Result<(Uuid, Arc<Mutex<AuthState>>), WarpgateError> {
|
||||
|
@ -63,7 +64,13 @@ impl AuthStateStore {
|
|||
return Err(WarpgateError::UserNotFound)
|
||||
};
|
||||
|
||||
let state = AuthState::new(id, username.to_string(), protocol.to_string(), policy);
|
||||
let state = AuthState::new(
|
||||
id,
|
||||
session_id.copied(),
|
||||
username.to_string(),
|
||||
protocol.to_string(),
|
||||
policy,
|
||||
);
|
||||
self.store
|
||||
.insert(id, (Arc::new(Mutex::new(state)), Instant::now()));
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use poem::session::Session;
|
||||
use poem::web::Data;
|
||||
use poem::Request;
|
||||
|
@ -66,7 +67,10 @@ enum LogoutResponse {
|
|||
#[derive(Object)]
|
||||
struct AuthStateResponseInternal {
|
||||
pub protocol: String,
|
||||
pub address: Option<String>,
|
||||
pub started: DateTime<Utc>,
|
||||
pub state: ApiAuthState,
|
||||
pub identification_string: String,
|
||||
}
|
||||
|
||||
#[derive(ApiResponse)]
|
||||
|
@ -214,7 +218,7 @@ impl Api {
|
|||
let Some(state_arc) = store.get(&state_id.0) else {
|
||||
return Ok(AuthStateResponse::NotFound);
|
||||
};
|
||||
serialize_auth_state_inner(state_arc).await
|
||||
serialize_auth_state_inner(state_arc, *services).await
|
||||
}
|
||||
|
||||
#[oai(
|
||||
|
@ -237,7 +241,7 @@ impl Api {
|
|||
state_arc.lock().await.reject();
|
||||
store.complete(&state_id.0).await;
|
||||
session.clear_auth_state();
|
||||
serialize_auth_state_inner(state_arc).await
|
||||
serialize_auth_state_inner(state_arc, *services).await
|
||||
}
|
||||
|
||||
#[oai(
|
||||
|
@ -256,7 +260,7 @@ impl Api {
|
|||
let Some(state_arc) = state_arc else {
|
||||
return Ok(AuthStateResponse::NotFound);
|
||||
};
|
||||
serialize_auth_state_inner(state_arc).await
|
||||
serialize_auth_state_inner(state_arc, *services).await
|
||||
}
|
||||
|
||||
#[oai(
|
||||
|
@ -284,7 +288,7 @@ impl Api {
|
|||
if let AuthResult::Accepted { .. } = auth_result {
|
||||
services.auth_state_store.lock().await.complete(&id).await;
|
||||
}
|
||||
serialize_auth_state_inner(state_arc).await
|
||||
serialize_auth_state_inner(state_arc, *services).await
|
||||
}
|
||||
|
||||
#[oai(
|
||||
|
@ -304,7 +308,7 @@ impl Api {
|
|||
};
|
||||
state_arc.lock().await.reject();
|
||||
services.auth_state_store.lock().await.complete(&id).await;
|
||||
serialize_auth_state_inner(state_arc).await
|
||||
serialize_auth_state_inner(state_arc, *services).await
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -339,10 +343,25 @@ async fn get_auth_state(
|
|||
|
||||
async fn serialize_auth_state_inner(
|
||||
state_arc: Arc<Mutex<AuthState>>,
|
||||
services: &Services,
|
||||
) -> poem::Result<AuthStateResponse> {
|
||||
let state = state_arc.lock().await;
|
||||
|
||||
let session_state_store = services.state.lock().await;
|
||||
let session_state = state
|
||||
.session_id()
|
||||
.and_then(|session_id| session_state_store.sessions.get(&session_id));
|
||||
|
||||
let peer_addr = match session_state {
|
||||
Some(x) => x.lock().await.remote_address,
|
||||
None => None,
|
||||
};
|
||||
|
||||
Ok(AuthStateResponse::Ok(Json(AuthStateResponseInternal {
|
||||
protocol: state.protocol().to_string(),
|
||||
address: peer_addr.map(|x| x.ip().to_string()),
|
||||
started: state.started().clone(),
|
||||
state: state.verify().into(),
|
||||
identification_string: state.identification_string().to_owned(),
|
||||
})))
|
||||
}
|
||||
|
|
|
@ -198,7 +198,9 @@ pub async fn get_auth_state_for_request(
|
|||
match session.get_auth_state_id() {
|
||||
Some(id) => Ok(store.get(&id.0).ok_or(WarpgateError::InconsistentState)?),
|
||||
None => {
|
||||
let (id, state) = store.create(username, crate::common::PROTOCOL_NAME).await?;
|
||||
let (id, state) = store
|
||||
.create(None, username, crate::common::PROTOCOL_NAME)
|
||||
.await?;
|
||||
session.set(AUTH_STATE_ID_SESSION_KEY, AuthStateId(id));
|
||||
Ok(state)
|
||||
}
|
||||
|
|
|
@ -183,7 +183,11 @@ impl MySqlSession {
|
|||
.auth_state_store
|
||||
.lock()
|
||||
.await
|
||||
.create(&username, crate::common::PROTOCOL_NAME)
|
||||
.create(
|
||||
Some(&self.server_handle.lock().await.id()),
|
||||
&username,
|
||||
crate::common::PROTOCOL_NAME,
|
||||
)
|
||||
.await?
|
||||
.1;
|
||||
let mut state = state_arc.lock().await;
|
||||
|
|
|
@ -228,7 +228,7 @@ impl ServerSession {
|
|||
.auth_state_store
|
||||
.lock()
|
||||
.await
|
||||
.create(username, crate::PROTOCOL_NAME)
|
||||
.create(Some(&self.id), username, crate::PROTOCOL_NAME)
|
||||
.await?
|
||||
.1;
|
||||
self.auth_state = Some(state);
|
||||
|
@ -1279,6 +1279,8 @@ impl ServerSession {
|
|||
let Some(auth_state) = self.auth_state.as_ref() else {
|
||||
return russh::server::Auth::Reject { proceed_with_methods: None};
|
||||
};
|
||||
let identification_string =
|
||||
auth_state.lock().await.identification_string().to_owned();
|
||||
let auth_state_id = *auth_state.lock().await.id();
|
||||
let event = self
|
||||
.services
|
||||
|
@ -1311,11 +1313,19 @@ impl ServerSession {
|
|||
russh::server::Auth::Partial {
|
||||
name: Cow::Owned(format!(
|
||||
concat!(
|
||||
"----------------------------------------------------------------\n",
|
||||
"Warpgate authentication: please open {} in your browser\n",
|
||||
"----------------------------------------------------------------\n"
|
||||
"-----------------------------------------------------------------------\n",
|
||||
"Warpgate authentication: please open the following URL in your browser:\n",
|
||||
"{}\n\n",
|
||||
"Make sure you're seeing this security key: {}\n",
|
||||
"-----------------------------------------------------------------------\n"
|
||||
),
|
||||
login_url
|
||||
login_url,
|
||||
identification_string
|
||||
.chars()
|
||||
.into_iter()
|
||||
.map(|x| x.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ")
|
||||
)),
|
||||
instructions: Cow::Borrowed(""),
|
||||
prompts: Cow::Owned(vec![(Cow::Borrowed("Press Enter when done: "), true)]),
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"openapi": "3.0.0",
|
||||
"info": {
|
||||
"title": "Warpgate Web Admin",
|
||||
"version": "0.7.1"
|
||||
"version": "0.7.4"
|
||||
},
|
||||
"servers": [
|
||||
{
|
||||
|
|
|
@ -4,6 +4,7 @@ import { Alert } from 'sveltestrap'
|
|||
import { api, ApiAuthState, AuthStateResponseInternal } from 'gateway/lib/api'
|
||||
import AsyncButton from 'common/AsyncButton.svelte'
|
||||
import DelayedSpinner from 'common/DelayedSpinner.svelte'
|
||||
import RelativeDate from 'admin/RelativeDate.svelte'
|
||||
|
||||
export let params: { stateId: string }
|
||||
let authState: AuthStateResponseInternal
|
||||
|
@ -29,6 +30,19 @@ async function reject () {
|
|||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.identification-string {
|
||||
display: flex;
|
||||
font-size: 3rem;
|
||||
|
||||
.card {
|
||||
padding: 0rem 0.5rem;
|
||||
border-radius: .5rem;
|
||||
margin-right: .5rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
{#await init()}
|
||||
<DelayedSpinner />
|
||||
{:then}
|
||||
|
@ -36,7 +50,25 @@ async function reject () {
|
|||
<h1>Authorization request</h1>
|
||||
</div>
|
||||
|
||||
<p>Authorize this {authState.protocol} session?</p>
|
||||
<div class="mb-5">
|
||||
<div class="mb-2">Ensure this security key matches your authentication prompt:</div>
|
||||
<div class="identification-string">
|
||||
{#each authState.identificationString as char}
|
||||
<div class="card bg-secondary text-light">
|
||||
<div class="card-body">{char}</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div> </div>
|
||||
|
||||
<div class="mb-3">
|
||||
<div>
|
||||
Authorize this {authState.protocol} session?
|
||||
</div>
|
||||
<small>
|
||||
Requested <RelativeDate date={authState.started} />
|
||||
{#if authState.address}from {authState.address}{/if}
|
||||
</small>
|
||||
</div>
|
||||
|
||||
{#if authState.state === ApiAuthState.Success}
|
||||
<Alert color="success">
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"openapi": "3.0.0",
|
||||
"info": {
|
||||
"title": "Warpgate HTTP proxy",
|
||||
"version": "0.7.1"
|
||||
"version": "0.7.4"
|
||||
},
|
||||
"servers": [
|
||||
{
|
||||
|
@ -386,14 +386,26 @@
|
|||
"type": "object",
|
||||
"required": [
|
||||
"protocol",
|
||||
"state"
|
||||
"started",
|
||||
"state",
|
||||
"identification_string"
|
||||
],
|
||||
"properties": {
|
||||
"protocol": {
|
||||
"type": "string"
|
||||
},
|
||||
"address": {
|
||||
"type": "string"
|
||||
},
|
||||
"started": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"state": {
|
||||
"$ref": "#/components/schemas/ApiAuthState"
|
||||
},
|
||||
"identification_string": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
Loading…
Reference in a new issue