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