mirror of
https://github.com/warp-tech/warpgate.git
synced 2024-09-20 06:46:17 +08:00
parent
116bf9fd4d
commit
fe521f2a39
101
Cargo.lock
generated
101
Cargo.lock
generated
|
@ -499,6 +499,12 @@ version = "0.21.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
|
checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "base64"
|
||||||
|
version = "0.22.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base64ct"
|
name = "base64ct"
|
||||||
version = "1.6.0"
|
version = "1.6.0"
|
||||||
|
@ -821,7 +827,7 @@ dependencies = [
|
||||||
"anstream",
|
"anstream",
|
||||||
"anstyle",
|
"anstyle",
|
||||||
"clap_lex 0.7.0",
|
"clap_lex 0.7.0",
|
||||||
"strsim 0.11.0",
|
"strsim 0.11.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1129,16 +1135,6 @@ dependencies = [
|
||||||
"syn 2.0.55",
|
"syn 2.0.55",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "darling"
|
|
||||||
version = "0.13.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c"
|
|
||||||
dependencies = [
|
|
||||||
"darling_core 0.13.4",
|
|
||||||
"darling_macro 0.13.4",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "darling"
|
name = "darling"
|
||||||
version = "0.14.4"
|
version = "0.14.4"
|
||||||
|
@ -1150,17 +1146,13 @@ dependencies = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "darling_core"
|
name = "darling"
|
||||||
version = "0.13.4"
|
version = "0.20.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610"
|
checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"fnv",
|
"darling_core 0.20.10",
|
||||||
"ident_case",
|
"darling_macro 0.20.10",
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"strsim 0.10.0",
|
|
||||||
"syn 1.0.109",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1178,14 +1170,17 @@ dependencies = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "darling_macro"
|
name = "darling_core"
|
||||||
version = "0.13.4"
|
version = "0.20.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835"
|
checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling_core 0.13.4",
|
"fnv",
|
||||||
|
"ident_case",
|
||||||
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 1.0.109",
|
"strsim 0.11.1",
|
||||||
|
"syn 2.0.55",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1199,6 +1194,17 @@ dependencies = [
|
||||||
"syn 1.0.109",
|
"syn 1.0.109",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "darling_macro"
|
||||||
|
version = "0.20.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
|
||||||
|
dependencies = [
|
||||||
|
"darling_core 0.20.10",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.55",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "data-encoding"
|
name = "data-encoding"
|
||||||
version = "2.5.0"
|
version = "2.5.0"
|
||||||
|
@ -1312,6 +1318,12 @@ version = "0.15.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
|
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dyn-clone"
|
||||||
|
version = "1.0.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ecdsa"
|
name = "ecdsa"
|
||||||
version = "0.16.9"
|
version = "0.16.9"
|
||||||
|
@ -2129,6 +2141,7 @@ checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
"hashbrown 0.12.3",
|
"hashbrown 0.12.3",
|
||||||
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2139,6 +2152,7 @@ checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"equivalent",
|
"equivalent",
|
||||||
"hashbrown 0.14.3",
|
"hashbrown 0.14.3",
|
||||||
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2776,19 +2790,23 @@ checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "openidconnect"
|
name = "openidconnect"
|
||||||
version = "2.5.1"
|
version = "3.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "98dd5b7049bac4fdd2233b8c9767d42c05da8006fdb79cc903258556d2b18009"
|
checksum = "f47e80a9cfae4462dd29c41e987edd228971d6565553fbc14b8a11e666d91590"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.13.1",
|
"base64 0.13.1",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
"dyn-clone",
|
||||||
|
"ed25519-dalek",
|
||||||
|
"hmac",
|
||||||
"http",
|
"http",
|
||||||
"itertools 0.10.5",
|
"itertools 0.10.5",
|
||||||
"log",
|
"log",
|
||||||
"num-bigint",
|
|
||||||
"oauth2",
|
"oauth2",
|
||||||
|
"p256",
|
||||||
|
"p384",
|
||||||
"rand",
|
"rand",
|
||||||
"ring 0.16.20",
|
"rsa",
|
||||||
"serde",
|
"serde",
|
||||||
"serde-value",
|
"serde-value",
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
|
@ -2796,6 +2814,7 @@ dependencies = [
|
||||||
"serde_path_to_error",
|
"serde_path_to_error",
|
||||||
"serde_plain",
|
"serde_plain",
|
||||||
"serde_with",
|
"serde_with",
|
||||||
|
"sha2",
|
||||||
"subtle",
|
"subtle",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"url",
|
"url",
|
||||||
|
@ -4336,24 +4355,32 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_with"
|
name = "serde_with"
|
||||||
version = "1.14.0"
|
version = "3.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "678b5a069e50bf00ecd22d0cd8ddf7c236f68581b03db652061ed5eb13a312ff"
|
checksum = "69cecfa94848272156ea67b2b1a53f20fc7bc638c4a46d2f8abde08f05f4b857"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"base64 0.22.1",
|
||||||
|
"chrono",
|
||||||
|
"hex",
|
||||||
|
"indexmap 1.9.3",
|
||||||
|
"indexmap 2.2.6",
|
||||||
"serde",
|
"serde",
|
||||||
|
"serde_derive",
|
||||||
|
"serde_json",
|
||||||
"serde_with_macros",
|
"serde_with_macros",
|
||||||
|
"time",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_with_macros"
|
name = "serde_with_macros"
|
||||||
version = "1.5.2"
|
version = "3.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082"
|
checksum = "a8fee4991ef4f274617a51ad4af30519438dacb2f56ac773b08a1922ff743350"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling 0.13.4",
|
"darling 0.20.10",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 1.0.109",
|
"syn 2.0.55",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -4830,9 +4857,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strsim"
|
name = "strsim"
|
||||||
version = "0.11.0"
|
version = "0.11.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01"
|
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strum"
|
name = "strum"
|
||||||
|
|
39
oidc-test/clients-config.json
Normal file
39
oidc-test/clients-config.json
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"ClientId": "implicit-mock-client",
|
||||||
|
"Description": "Client for implicit flow",
|
||||||
|
"AllowedGrantTypes": ["implicit"],
|
||||||
|
"AllowAccessTokensViaBrowser": true,
|
||||||
|
"RedirectUris": [
|
||||||
|
"https://warpgate.com/@warpgate/api/sso/return",
|
||||||
|
"https://127.0.0.1:8888/@warpgate/api/sso/return"
|
||||||
|
],
|
||||||
|
"AllowedScopes": ["openid", "profile", "email", "warpgate-scope"],
|
||||||
|
"IdentityTokenLifetime": 3600,
|
||||||
|
"AccessTokenLifetime": 3600
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ClientId": "client-credentials-mock-client",
|
||||||
|
"ClientSecrets": ["client-credentials-mock-client-secret"],
|
||||||
|
"Description": "Client for client credentials flow",
|
||||||
|
"AllowedGrantTypes": ["authorization_code"],
|
||||||
|
"AllowedScopes": ["openid", "profile", "email", "warpgate-scope"],
|
||||||
|
"ClientClaimsPrefix": "",
|
||||||
|
"RedirectUris": [
|
||||||
|
"https://warpgate.com/@warpgate/api/sso/return",
|
||||||
|
"https://127.0.0.1:8888/@warpgate/api/sso/return"
|
||||||
|
],
|
||||||
|
"Claims": [
|
||||||
|
{
|
||||||
|
"Type": "string_claim",
|
||||||
|
"Value": "string_claim_value",
|
||||||
|
"ValueType": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Type": "json_claim",
|
||||||
|
"Value": "[\"value1\", \"value2\"]",
|
||||||
|
"ValueType": "json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
|
@ -4,7 +4,7 @@ services:
|
||||||
container_name: oidc-server-mock
|
container_name: oidc-server-mock
|
||||||
image: ghcr.io/soluto/oidc-server-mock:latest
|
image: ghcr.io/soluto/oidc-server-mock:latest
|
||||||
ports:
|
ports:
|
||||||
- '4011:80'
|
- '4011:8080'
|
||||||
environment:
|
environment:
|
||||||
ASPNETCORE_ENVIRONMENT: Development
|
ASPNETCORE_ENVIRONMENT: Development
|
||||||
SERVER_OPTIONS_INLINE: |
|
SERVER_OPTIONS_INLINE: |
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use std::ops::DerefMut;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
|
@ -14,6 +15,7 @@ use warpgate_common::auth::{AuthCredential, AuthResult, AuthState, CredentialKin
|
||||||
use warpgate_common::{Secret, WarpgateError};
|
use warpgate_common::{Secret, WarpgateError};
|
||||||
use warpgate_core::Services;
|
use warpgate_core::Services;
|
||||||
|
|
||||||
|
use super::common::logout;
|
||||||
use crate::common::{
|
use crate::common::{
|
||||||
authorize_session, endpoint_auth, get_auth_state_for_request, SessionAuthorization, SessionExt,
|
authorize_session, endpoint_auth, get_auth_state_for_request, SessionAuthorization, SessionExt,
|
||||||
};
|
};
|
||||||
|
@ -209,9 +211,7 @@ impl Api {
|
||||||
session: &Session,
|
session: &Session,
|
||||||
session_middleware: Data<&Arc<Mutex<SessionStore>>>,
|
session_middleware: Data<&Arc<Mutex<SessionStore>>>,
|
||||||
) -> poem::Result<LogoutResponse> {
|
) -> poem::Result<LogoutResponse> {
|
||||||
session_middleware.lock().await.remove_session(session);
|
logout(session, session_middleware.lock().await.deref_mut());
|
||||||
session.clear();
|
|
||||||
info!("Logged out");
|
|
||||||
Ok(LogoutResponse::Success)
|
Ok(LogoutResponse::Success)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
10
warpgate-protocol-http/src/api/common.rs
Normal file
10
warpgate-protocol-http/src/api/common.rs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
use poem::session::Session;
|
||||||
|
use tracing::info;
|
||||||
|
|
||||||
|
use crate::session::SessionStore;
|
||||||
|
|
||||||
|
pub fn logout(session: &Session, session_middleware: &mut SessionStore) {
|
||||||
|
session_middleware.remove_session(session);
|
||||||
|
session.clear();
|
||||||
|
info!("Logged out");
|
||||||
|
}
|
|
@ -25,6 +25,7 @@ pub struct Info {
|
||||||
external_host: Option<String>,
|
external_host: Option<String>,
|
||||||
ports: PortsInfo,
|
ports: PortsInfo,
|
||||||
authorized_via_ticket: bool,
|
authorized_via_ticket: bool,
|
||||||
|
authorized_via_sso_with_single_logout: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(ApiResponse)]
|
#[derive(ApiResponse)]
|
||||||
|
@ -64,6 +65,9 @@ impl Api {
|
||||||
session.get_auth(),
|
session.get_auth(),
|
||||||
Some(SessionAuthorization::Ticket { .. })
|
Some(SessionAuthorization::Ticket { .. })
|
||||||
),
|
),
|
||||||
|
authorized_via_sso_with_single_logout: session
|
||||||
|
.get_sso_login_state()
|
||||||
|
.map_or(false, |state| state.supports_single_logout),
|
||||||
ports: if session.is_authenticated() {
|
ports: if session.is_authenticated() {
|
||||||
PortsInfo {
|
PortsInfo {
|
||||||
ssh: if config.store.ssh.enable {
|
ssh: if config.store.ssh.enable {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use poem_openapi::OpenApi;
|
use poem_openapi::OpenApi;
|
||||||
|
|
||||||
pub mod auth;
|
pub mod auth;
|
||||||
|
mod common;
|
||||||
pub mod info;
|
pub mod info;
|
||||||
pub mod sso_provider_detail;
|
pub mod sso_provider_detail;
|
||||||
pub mod sso_provider_list;
|
pub mod sso_provider_list;
|
||||||
|
|
|
@ -5,6 +5,7 @@ use poem_openapi::param::{Path, Query};
|
||||||
use poem_openapi::payload::Json;
|
use poem_openapi::payload::Json;
|
||||||
use poem_openapi::{ApiResponse, Object, OpenApi};
|
use poem_openapi::{ApiResponse, Object, OpenApi};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tracing::*;
|
||||||
use warpgate_core::Services;
|
use warpgate_core::Services;
|
||||||
use warpgate_sso::{SsoClient, SsoLoginRequest};
|
use warpgate_sso::{SsoClient, SsoLoginRequest};
|
||||||
|
|
||||||
|
@ -31,6 +32,7 @@ pub struct SsoContext {
|
||||||
pub provider: String,
|
pub provider: String,
|
||||||
pub request: SsoLoginRequest,
|
pub request: SsoLoginRequest,
|
||||||
pub next_url: Option<String>,
|
pub next_url: Option<String>,
|
||||||
|
pub supports_single_logout: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[OpenApi]
|
#[OpenApi]
|
||||||
|
@ -54,6 +56,7 @@ impl Api {
|
||||||
|
|
||||||
let mut return_url = config.construct_external_url(Some(req))?;
|
let mut return_url = config.construct_external_url(Some(req))?;
|
||||||
return_url.set_path("@warpgate/api/sso/return");
|
return_url.set_path("@warpgate/api/sso/return");
|
||||||
|
debug!("Return URL: {}", &return_url);
|
||||||
|
|
||||||
let Some(provider_config) = config.store.sso_providers.iter().find(|p| p.name == *name)
|
let Some(provider_config) = config.store.sso_providers.iter().find(|p| p.name == *name)
|
||||||
else {
|
else {
|
||||||
|
@ -74,6 +77,10 @@ impl Api {
|
||||||
provider: name,
|
provider: name,
|
||||||
request: sso_req,
|
request: sso_req,
|
||||||
next_url: next.0.clone(),
|
next_url: next.0.clone(),
|
||||||
|
supports_single_logout: client
|
||||||
|
.supports_single_logout()
|
||||||
|
.await
|
||||||
|
.map_err(poem::error::InternalServerError)?,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
use std::ops::DerefMut;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use poem::session::Session;
|
use poem::session::Session;
|
||||||
use poem::web::{Data, Form};
|
use poem::web::{Data, Form};
|
||||||
use poem::Request;
|
use poem::Request;
|
||||||
|
@ -5,13 +8,17 @@ use poem_openapi::param::Query;
|
||||||
use poem_openapi::payload::{Html, Json, Response};
|
use poem_openapi::payload::{Html, Json, Response};
|
||||||
use poem_openapi::{ApiResponse, Enum, Object, OpenApi};
|
use poem_openapi::{ApiResponse, Enum, Object, OpenApi};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
use tokio::sync::Mutex;
|
||||||
use tracing::*;
|
use tracing::*;
|
||||||
use warpgate_common::auth::{AuthCredential, AuthResult};
|
use warpgate_common::auth::{AuthCredential, AuthResult};
|
||||||
use warpgate_core::Services;
|
use warpgate_core::Services;
|
||||||
use warpgate_sso::SsoInternalProviderConfig;
|
use warpgate_sso::{SsoClient, SsoInternalProviderConfig};
|
||||||
|
|
||||||
use super::sso_provider_detail::{SsoContext, SSO_CONTEXT_SESSION_KEY};
|
use super::sso_provider_detail::{SsoContext, SSO_CONTEXT_SESSION_KEY};
|
||||||
use crate::common::{authorize_session, get_auth_state_for_request};
|
use crate::api::common::logout;
|
||||||
|
use crate::common::{authorize_session, get_auth_state_for_request, SessionExt};
|
||||||
|
use crate::session::SessionStore;
|
||||||
|
use crate::SsoLoginState;
|
||||||
|
|
||||||
pub struct Api;
|
pub struct Api;
|
||||||
|
|
||||||
|
@ -55,6 +62,22 @@ pub struct ReturnToSsoFormData {
|
||||||
pub code: Option<String>,
|
pub code: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Object)]
|
||||||
|
struct StartSloResponseParams {
|
||||||
|
url: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::large_enum_variant)]
|
||||||
|
#[derive(ApiResponse)]
|
||||||
|
enum StartSloResponse {
|
||||||
|
#[oai(status = 200)]
|
||||||
|
Ok(Json<StartSloResponseParams>),
|
||||||
|
#[oai(status = 400)]
|
||||||
|
NotInSsoSession,
|
||||||
|
#[oai(status = 404)]
|
||||||
|
NotFound,
|
||||||
|
}
|
||||||
|
|
||||||
fn make_redirect_url(err: &str) -> String {
|
fn make_redirect_url(err: &str) -> String {
|
||||||
error!("SSO error: {err}");
|
error!("SSO error: {err}");
|
||||||
format!("/@warpgate?login_error={err}")
|
format!("/@warpgate?login_error={err}")
|
||||||
|
@ -175,7 +198,7 @@ impl Api {
|
||||||
|
|
||||||
let provider = context.provider.clone();
|
let provider = context.provider.clone();
|
||||||
let cred = AuthCredential::Sso {
|
let cred = AuthCredential::Sso {
|
||||||
provider: context.provider,
|
provider: context.provider.clone(),
|
||||||
email: email.clone(),
|
email: email.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -204,11 +227,20 @@ impl Api {
|
||||||
|
|
||||||
if cp.validate_credential(&username, &cred).await? {
|
if cp.validate_credential(&username, &cred).await? {
|
||||||
state.add_valid_credential(cred);
|
state.add_valid_credential(cred);
|
||||||
|
} else {
|
||||||
|
return Ok(Err(format!(
|
||||||
|
"Failed to validate SSO credential for {username}"
|
||||||
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let AuthResult::Accepted { username } = state.verify() {
|
if let AuthResult::Accepted { username } = state.verify() {
|
||||||
auth_state_store.complete(state.id()).await;
|
auth_state_store.complete(state.id()).await;
|
||||||
authorize_session(req, username).await?;
|
authorize_session(req, username).await?;
|
||||||
|
session.set_sso_login_state(SsoLoginState {
|
||||||
|
provider: context.provider,
|
||||||
|
token: response.id_token,
|
||||||
|
supports_single_logout: context.supports_single_logout,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let providers_config = services.config.lock().await.store.sso_providers.clone();
|
let providers_config = services.config.lock().await.store.sso_providers.clone();
|
||||||
|
@ -249,4 +281,47 @@ impl Api {
|
||||||
.unwrap_or("/@warpgate#/login")
|
.unwrap_or("/@warpgate#/login")
|
||||||
.to_owned()))
|
.to_owned()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[oai(
|
||||||
|
path = "/sso/logout",
|
||||||
|
method = "get",
|
||||||
|
operation_id = "initiate_sso_logout"
|
||||||
|
)]
|
||||||
|
async fn api_start_slo(
|
||||||
|
&self,
|
||||||
|
req: &Request,
|
||||||
|
session: &Session,
|
||||||
|
services: Data<&Services>,
|
||||||
|
session_middleware: Data<&Arc<Mutex<SessionStore>>>,
|
||||||
|
) -> poem::Result<StartSloResponse> {
|
||||||
|
let Some(state) = session.get_sso_login_state() else {
|
||||||
|
return Ok(StartSloResponse::NotInSsoSession);
|
||||||
|
};
|
||||||
|
|
||||||
|
let config = services.config.lock().await;
|
||||||
|
|
||||||
|
let return_url = config.construct_external_url(Some(req))?;
|
||||||
|
debug!("Return URL: {}", &return_url);
|
||||||
|
|
||||||
|
let Some(provider_config) = config
|
||||||
|
.store
|
||||||
|
.sso_providers
|
||||||
|
.iter()
|
||||||
|
.find(|p| p.name == state.provider)
|
||||||
|
else {
|
||||||
|
return Ok(StartSloResponse::NotFound);
|
||||||
|
};
|
||||||
|
|
||||||
|
let client = SsoClient::new(provider_config.provider.clone());
|
||||||
|
let logout_url = client
|
||||||
|
.logout(state.token, return_url)
|
||||||
|
.await
|
||||||
|
.map_err(poem::error::InternalServerError)?;
|
||||||
|
|
||||||
|
logout(session, session_middleware.lock().await.deref_mut());
|
||||||
|
|
||||||
|
Ok(StartSloResponse::Ok(Json(StartSloResponseParams {
|
||||||
|
url: logout_url.to_string(),
|
||||||
|
})))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ use uuid::Uuid;
|
||||||
use warpgate_common::auth::{AuthState, CredentialKind};
|
use warpgate_common::auth::{AuthState, CredentialKind};
|
||||||
use warpgate_common::{ProtocolName, TargetOptions, WarpgateError};
|
use warpgate_common::{ProtocolName, TargetOptions, WarpgateError};
|
||||||
use warpgate_core::{AuthStateStore, Services};
|
use warpgate_core::{AuthStateStore, Services};
|
||||||
|
use warpgate_sso::CoreIdToken;
|
||||||
|
|
||||||
use crate::session::SessionStore;
|
use crate::session::SessionStore;
|
||||||
|
|
||||||
|
@ -18,8 +19,16 @@ pub const PROTOCOL_NAME: ProtocolName = "HTTP";
|
||||||
static TARGET_SESSION_KEY: &str = "target_name";
|
static TARGET_SESSION_KEY: &str = "target_name";
|
||||||
static AUTH_SESSION_KEY: &str = "auth";
|
static AUTH_SESSION_KEY: &str = "auth";
|
||||||
static AUTH_STATE_ID_SESSION_KEY: &str = "auth_state_id";
|
static AUTH_STATE_ID_SESSION_KEY: &str = "auth_state_id";
|
||||||
|
static AUTH_SSO_LOGIN_STATE: &str = "auth_sso_login_state";
|
||||||
pub static SESSION_COOKIE_NAME: &str = "warpgate-http-session";
|
pub static SESSION_COOKIE_NAME: &str = "warpgate-http-session";
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct SsoLoginState {
|
||||||
|
pub token: CoreIdToken,
|
||||||
|
pub provider: String,
|
||||||
|
pub supports_single_logout: bool,
|
||||||
|
}
|
||||||
|
|
||||||
pub trait SessionExt {
|
pub trait SessionExt {
|
||||||
fn get_target_name(&self) -> Option<String>;
|
fn get_target_name(&self) -> Option<String>;
|
||||||
fn set_target_name(&self, target_name: String);
|
fn set_target_name(&self, target_name: String);
|
||||||
|
@ -29,6 +38,9 @@ pub trait SessionExt {
|
||||||
fn set_auth(&self, auth: SessionAuthorization);
|
fn set_auth(&self, auth: SessionAuthorization);
|
||||||
fn get_auth_state_id(&self) -> Option<AuthStateId>;
|
fn get_auth_state_id(&self) -> Option<AuthStateId>;
|
||||||
fn clear_auth_state(&self);
|
fn clear_auth_state(&self);
|
||||||
|
|
||||||
|
fn get_sso_login_state(&self) -> Option<SsoLoginState>;
|
||||||
|
fn set_sso_login_state(&self, token: SsoLoginState);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SessionExt for Session {
|
impl SessionExt for Session {
|
||||||
|
@ -63,6 +75,17 @@ impl SessionExt for Session {
|
||||||
fn clear_auth_state(&self) {
|
fn clear_auth_state(&self) {
|
||||||
self.remove(AUTH_STATE_ID_SESSION_KEY)
|
self.remove(AUTH_STATE_ID_SESSION_KEY)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_sso_login_state(&self) -> Option<SsoLoginState> {
|
||||||
|
self.get::<String>(AUTH_SSO_LOGIN_STATE)
|
||||||
|
.and_then(|x| serde_json::from_str(&x).ok())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_sso_login_state(&self, state: SsoLoginState) {
|
||||||
|
if let Ok(json) = serde_json::to_string(&state) {
|
||||||
|
self.set(AUTH_SSO_LOGIN_STATE, json)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize)]
|
#[derive(Clone, Serialize, Deserialize)]
|
||||||
|
|
|
@ -17,7 +17,7 @@ use std::time::Duration;
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use common::page_admin_auth;
|
use common::page_admin_auth;
|
||||||
pub use common::PROTOCOL_NAME;
|
pub use common::{PROTOCOL_NAME, SsoLoginState};
|
||||||
use http::HeaderValue;
|
use http::HeaderValue;
|
||||||
use logging::{get_client_ip, log_request_result, span_for_request};
|
use logging::{get_client_ip, log_request_result, span_for_request};
|
||||||
use poem::endpoint::{EmbeddedFileEndpoint, EmbeddedFilesEndpoint};
|
use poem::endpoint::{EmbeddedFileEndpoint, EmbeddedFilesEndpoint};
|
||||||
|
|
|
@ -9,7 +9,7 @@ bytes = "1.3"
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
tokio = { version = "1.20", features = ["tracing", "macros"] }
|
tokio = { version = "1.20", features = ["tracing", "macros"] }
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
openidconnect = { version = "2.4", features = ["reqwest", "rustls-tls", "accept-string-booleans"] }
|
openidconnect = { version = "3.5", features = ["reqwest", "rustls-tls", "accept-string-booleans"] }
|
||||||
serde = "1.0"
|
serde = "1.0"
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
once_cell = "1.17"
|
once_cell = "1.17"
|
||||||
|
|
|
@ -24,6 +24,8 @@ pub enum SsoError {
|
||||||
Io(#[from] std::io::Error),
|
Io(#[from] std::io::Error),
|
||||||
#[error("JWT error: {0}")]
|
#[error("JWT error: {0}")]
|
||||||
Jwt(#[from] jsonwebtoken::errors::Error),
|
Jwt(#[from] jsonwebtoken::errors::Error),
|
||||||
|
#[error("the OIDC provider doesn't support RP-initiated logout")]
|
||||||
|
LogoutNotSupported,
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Other(Box<dyn Error + Send + Sync>),
|
Other(Box<dyn Error + Send + Sync>),
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,3 +9,5 @@ pub use error::*;
|
||||||
pub use request::*;
|
pub use request::*;
|
||||||
pub use response::*;
|
pub use response::*;
|
||||||
pub use sso::*;
|
pub use sso::*;
|
||||||
|
|
||||||
|
pub use openidconnect::core::CoreIdToken;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use futures::future::OptionFuture;
|
use futures::future::OptionFuture;
|
||||||
use openidconnect::core::CoreGenderClaim;
|
use openidconnect::core::{CoreGenderClaim, CoreIdToken};
|
||||||
use openidconnect::reqwest::async_http_client;
|
use openidconnect::reqwest::async_http_client;
|
||||||
use openidconnect::url::Url;
|
use openidconnect::url::Url;
|
||||||
use openidconnect::{
|
use openidconnect::{
|
||||||
|
@ -63,7 +63,7 @@ impl SsoLoginRequest {
|
||||||
e => SsoError::Verification(format!("{e}")),
|
e => SsoError::Verification(format!("{e}")),
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let id_token = token_response.id_token().ok_or(SsoError::NotOidc)?;
|
let id_token: &CoreIdToken = token_response.id_token().ok_or(SsoError::NotOidc)?;
|
||||||
let claims = id_token.claims(&client.id_token_verifier(), &self.nonce)?;
|
let claims = id_token.claims(&client.id_token_verifier(), &self.nonce)?;
|
||||||
|
|
||||||
let user_info_req = client
|
let user_info_req = client
|
||||||
|
@ -119,6 +119,8 @@ impl SsoLoginRequest {
|
||||||
email_verified: get_claim!(email_verified),
|
email_verified: get_claim!(email_verified),
|
||||||
|
|
||||||
groups: userinfo_claims.and_then(|x| x.additional_claims().warpgate_roles.clone()),
|
groups: userinfo_claims.and_then(|x| x.additional_claims().warpgate_roles.clone()),
|
||||||
|
|
||||||
|
id_token: id_token.clone(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
|
use openidconnect::core::CoreIdToken;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct SsoLoginResponse {
|
pub struct SsoLoginResponse {
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
pub email: Option<String>,
|
pub email: Option<String>,
|
||||||
pub email_verified: Option<bool>,
|
pub email_verified: Option<bool>,
|
||||||
pub groups: Option<Vec<String>>,
|
pub groups: Option<Vec<String>>,
|
||||||
|
pub id_token: CoreIdToken,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
|
||||||
use openidconnect::core::{CoreAuthenticationFlow, CoreClient, CoreProviderMetadata};
|
use openidconnect::core::{CoreAuthenticationFlow, CoreClient, CoreIdToken};
|
||||||
use openidconnect::reqwest::async_http_client;
|
use openidconnect::reqwest::async_http_client;
|
||||||
use openidconnect::{CsrfToken, DiscoveryError, Nonce, PkceCodeChallenge, RedirectUrl, Scope};
|
use openidconnect::url::Url;
|
||||||
|
use openidconnect::{
|
||||||
|
CsrfToken, DiscoveryError, LogoutRequest, Nonce, PkceCodeChallenge, PostLogoutRedirectUrl,
|
||||||
|
ProviderMetadataWithLogout, RedirectUrl, Scope,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::config::SsoInternalProviderConfig;
|
use crate::config::SsoInternalProviderConfig;
|
||||||
use crate::request::SsoLoginRequest;
|
use crate::request::SsoLoginRequest;
|
||||||
|
@ -13,15 +17,21 @@ pub struct SsoClient {
|
||||||
config: SsoInternalProviderConfig,
|
config: SsoInternalProviderConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn make_client(config: &SsoInternalProviderConfig) -> Result<CoreClient, SsoError> {
|
pub async fn discover_metadata(
|
||||||
let metadata = CoreProviderMetadata::discover_async(config.issuer_url()?, async_http_client)
|
config: &SsoInternalProviderConfig,
|
||||||
|
) -> Result<ProviderMetadataWithLogout, SsoError> {
|
||||||
|
ProviderMetadataWithLogout::discover_async(config.issuer_url()?, async_http_client)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
SsoError::Discovery(match e {
|
SsoError::Discovery(match e {
|
||||||
DiscoveryError::Request(inner) => format!("Request error: {inner}"),
|
DiscoveryError::Request(inner) => format!("Request error: {inner}"),
|
||||||
e => format!("{e}"),
|
e => format!("{e}"),
|
||||||
})
|
})
|
||||||
})?;
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn make_client(config: &SsoInternalProviderConfig) -> Result<CoreClient, SsoError> {
|
||||||
|
let metadata = discover_metadata(config).await?;
|
||||||
|
|
||||||
let client = CoreClient::from_provider_metadata(
|
let client = CoreClient::from_provider_metadata(
|
||||||
metadata,
|
metadata,
|
||||||
|
@ -44,6 +54,14 @@ impl SsoClient {
|
||||||
Self { config }
|
Self { config }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn supports_single_logout(&self) -> Result<bool, SsoError> {
|
||||||
|
let metadata = discover_metadata(&self.config).await?;
|
||||||
|
Ok(metadata
|
||||||
|
.additional_metadata()
|
||||||
|
.end_session_endpoint
|
||||||
|
.is_some())
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn start_login(&self, redirect_url: String) -> Result<SsoLoginRequest, SsoError> {
|
pub async fn start_login(&self, redirect_url: String) -> Result<SsoLoginRequest, SsoError> {
|
||||||
let redirect_url = RedirectUrl::new(redirect_url)?;
|
let redirect_url = RedirectUrl::new(redirect_url)?;
|
||||||
let client = make_client(&self.config).await?;
|
let client = make_client(&self.config).await?;
|
||||||
|
@ -82,4 +100,16 @@ impl SsoClient {
|
||||||
config: self.config.clone(),
|
config: self.config.clone(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn logout(&self, token: CoreIdToken, redirect_url: Url) -> Result<Url, SsoError> {
|
||||||
|
let metadata = discover_metadata(&self.config).await?;
|
||||||
|
let Some(ref url) = metadata.additional_metadata().end_session_endpoint else {
|
||||||
|
return Err(SsoError::LogoutNotSupported);
|
||||||
|
};
|
||||||
|
let mut req: LogoutRequest = url.clone().into();
|
||||||
|
req = req.set_id_token_hint(&token);
|
||||||
|
req = req.set_client_id(self.config.client_id().clone());
|
||||||
|
req = req.set_post_logout_redirect_uri(PostLogoutRedirectUrl::from_url(redirect_url));
|
||||||
|
return Ok(req.http_get_url());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { faSignOut } from '@fortawesome/free-solid-svg-icons'
|
|
||||||
import { api } from 'gateway/lib/api'
|
|
||||||
import { serverInfo, reloadServerInfo } from 'gateway/lib/store'
|
import { serverInfo, reloadServerInfo } from 'gateway/lib/store'
|
||||||
import Fa from 'svelte-fa'
|
|
||||||
|
|
||||||
import Router, { link } from 'svelte-spa-router'
|
import Router, { link } from 'svelte-spa-router'
|
||||||
import active from 'svelte-spa-router/active'
|
import active from 'svelte-spa-router/active'
|
||||||
|
@ -10,17 +7,12 @@ import { wrap } from 'svelte-spa-router/wrap'
|
||||||
import ThemeSwitcher from 'common/ThemeSwitcher.svelte'
|
import ThemeSwitcher from 'common/ThemeSwitcher.svelte'
|
||||||
import Logo from 'common/Logo.svelte'
|
import Logo from 'common/Logo.svelte'
|
||||||
import DelayedSpinner from 'common/DelayedSpinner.svelte'
|
import DelayedSpinner from 'common/DelayedSpinner.svelte'
|
||||||
|
import AuthBar from 'common/AuthBar.svelte'
|
||||||
|
|
||||||
async function init () {
|
async function init () {
|
||||||
await reloadServerInfo()
|
await reloadServerInfo()
|
||||||
}
|
}
|
||||||
|
|
||||||
async function logout () {
|
|
||||||
await api.logout()
|
|
||||||
await reloadServerInfo()
|
|
||||||
location.href = '/@warpgate'
|
|
||||||
}
|
|
||||||
|
|
||||||
init()
|
init()
|
||||||
|
|
||||||
const routes = {
|
const routes = {
|
||||||
|
@ -86,14 +78,7 @@ const routes = {
|
||||||
<a use:link use:active href="/ssh">SSH</a>
|
<a use:link use:active href="/ssh">SSH</a>
|
||||||
<a use:link use:active href="/log">Log</a>
|
<a use:link use:active href="/log">Log</a>
|
||||||
{/if}
|
{/if}
|
||||||
{#if $serverInfo?.username}
|
<AuthBar />
|
||||||
<div class="username ms-auto">
|
|
||||||
{$serverInfo?.username}
|
|
||||||
</div>
|
|
||||||
<button class="btn btn-link" on:click={logout} title="Log out">
|
|
||||||
<Fa icon={faSignOut} fw />
|
|
||||||
</button>
|
|
||||||
{/if}
|
|
||||||
</header>
|
</header>
|
||||||
<main>
|
<main>
|
||||||
<Router {routes}/>
|
<Router {routes}/>
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"openapi": "3.0.0",
|
"openapi": "3.0.0",
|
||||||
"info": {
|
"info": {
|
||||||
"title": "Warpgate Web Admin",
|
"title": "Warpgate Web Admin",
|
||||||
"version": "0.9.1"
|
"version": "0.10.2"
|
||||||
},
|
},
|
||||||
"servers": [
|
"servers": [
|
||||||
{
|
{
|
||||||
|
|
50
warpgate-web/src/common/AuthBar.svelte
Normal file
50
warpgate-web/src/common/AuthBar.svelte
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { faSignOut } from '@fortawesome/free-solid-svg-icons'
|
||||||
|
import Fa from 'svelte-fa'
|
||||||
|
|
||||||
|
import { api } from 'gateway/lib/api'
|
||||||
|
import { serverInfo, reloadServerInfo } from 'gateway/lib/store'
|
||||||
|
import { Button, Dropdown, DropdownItem, DropdownMenu, DropdownToggle } from '@sveltestrap/sveltestrap'
|
||||||
|
|
||||||
|
async function logout () {
|
||||||
|
await api.logout()
|
||||||
|
await reloadServerInfo()
|
||||||
|
location.href = '/@warpgate'
|
||||||
|
}
|
||||||
|
|
||||||
|
async function singleLogout () {
|
||||||
|
const response = await api.initiateSsoLogout()
|
||||||
|
location.href = response.url
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if $serverInfo?.username}
|
||||||
|
<div class="ms-auto">
|
||||||
|
{$serverInfo.username}
|
||||||
|
{#if $serverInfo.authorizedViaTicket}
|
||||||
|
<span class="ml-2">(ticket auth)</span>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if $serverInfo?.authorizedViaSsoWithSingleLogout}
|
||||||
|
<Dropdown>
|
||||||
|
<DropdownToggle color="link" title="Log out options">
|
||||||
|
<Fa icon={faSignOut} fw />
|
||||||
|
</DropdownToggle>
|
||||||
|
<DropdownMenu right={true}>
|
||||||
|
<DropdownItem on:click={logout}>
|
||||||
|
<Fa icon={faSignOut} fw />
|
||||||
|
Log out of Warpgate
|
||||||
|
</DropdownItem>
|
||||||
|
<DropdownItem on:click={singleLogout}>
|
||||||
|
<Fa icon={faSignOut} fw />
|
||||||
|
Log out everywhere
|
||||||
|
</DropdownItem>
|
||||||
|
</DropdownMenu>
|
||||||
|
</Dropdown>
|
||||||
|
{:else}
|
||||||
|
<Button color="link" on:click={logout} title="Log out">
|
||||||
|
<Fa icon={faSignOut} fw />
|
||||||
|
</Button>
|
||||||
|
{/if}
|
||||||
|
{/if}
|
|
@ -1,15 +1,13 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { faSignOut } from '@fortawesome/free-solid-svg-icons'
|
|
||||||
import { Alert } from '@sveltestrap/sveltestrap'
|
import { Alert } from '@sveltestrap/sveltestrap'
|
||||||
import Fa from 'svelte-fa'
|
|
||||||
import Router, { push, type RouteDetail } from 'svelte-spa-router'
|
import Router, { push, type RouteDetail } from 'svelte-spa-router'
|
||||||
import { wrap } from 'svelte-spa-router/wrap'
|
import { wrap } from 'svelte-spa-router/wrap'
|
||||||
import { get } from 'svelte/store'
|
import { get } from 'svelte/store'
|
||||||
import { api } from 'gateway/lib/api'
|
|
||||||
import { reloadServerInfo, serverInfo } from 'gateway/lib/store'
|
import { reloadServerInfo, serverInfo } from 'gateway/lib/store'
|
||||||
import ThemeSwitcher from 'common/ThemeSwitcher.svelte'
|
import ThemeSwitcher from 'common/ThemeSwitcher.svelte'
|
||||||
import Logo from 'common/Logo.svelte'
|
import Logo from 'common/Logo.svelte'
|
||||||
import DelayedSpinner from 'common/DelayedSpinner.svelte'
|
import DelayedSpinner from 'common/DelayedSpinner.svelte'
|
||||||
|
import AuthBar from 'common/AuthBar.svelte'
|
||||||
|
|
||||||
let redirecting = false
|
let redirecting = false
|
||||||
let serverInfoPromise = reloadServerInfo()
|
let serverInfoPromise = reloadServerInfo()
|
||||||
|
@ -18,12 +16,6 @@ async function init () {
|
||||||
await serverInfoPromise
|
await serverInfoPromise
|
||||||
}
|
}
|
||||||
|
|
||||||
async function logout () {
|
|
||||||
await api.logout()
|
|
||||||
await reloadServerInfo()
|
|
||||||
push('/login')
|
|
||||||
}
|
|
||||||
|
|
||||||
function onPageResume () {
|
function onPageResume () {
|
||||||
redirecting = false
|
redirecting = false
|
||||||
init()
|
init()
|
||||||
|
@ -76,17 +68,7 @@ init()
|
||||||
<Logo />
|
<Logo />
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
{#if $serverInfo?.username}
|
<AuthBar />
|
||||||
<div class="ms-auto">
|
|
||||||
{$serverInfo.username}
|
|
||||||
{#if $serverInfo.authorizedViaTicket}
|
|
||||||
<span class="ml-2">(ticket auth)</span>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
<button class="btn btn-link" on:click={logout} title="Log out">
|
|
||||||
<Fa icon={faSignOut} fw />
|
|
||||||
</button>
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"openapi": "3.0.0",
|
"openapi": "3.0.0",
|
||||||
"info": {
|
"info": {
|
||||||
"title": "Warpgate HTTP proxy",
|
"title": "Warpgate HTTP proxy",
|
||||||
"version": "0.9.1"
|
"version": "0.10.2"
|
||||||
},
|
},
|
||||||
"servers": [
|
"servers": [
|
||||||
{
|
{
|
||||||
|
@ -324,6 +324,29 @@
|
||||||
"operationId": "return_to_sso_with_form_data"
|
"operationId": "return_to_sso_with_form_data"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/sso/logout": {
|
||||||
|
"get": {
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "",
|
||||||
|
"content": {
|
||||||
|
"application/json; charset=utf-8": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/StartSloResponseParams"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"operationId": "initiate_sso_logout"
|
||||||
|
}
|
||||||
|
},
|
||||||
"/sso/providers/{name}/start": {
|
"/sso/providers/{name}/start": {
|
||||||
"get": {
|
"get": {
|
||||||
"parameters": [
|
"parameters": [
|
||||||
|
@ -414,7 +437,8 @@
|
||||||
"required": [
|
"required": [
|
||||||
"version",
|
"version",
|
||||||
"ports",
|
"ports",
|
||||||
"authorized_via_ticket"
|
"authorized_via_ticket",
|
||||||
|
"authorized_via_sso_with_single_logout"
|
||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"version": {
|
"version": {
|
||||||
|
@ -434,6 +458,9 @@
|
||||||
},
|
},
|
||||||
"authorized_via_ticket": {
|
"authorized_via_ticket": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"authorized_via_sso_with_single_logout": {
|
||||||
|
"type": "boolean"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -519,6 +546,17 @@
|
||||||
"Custom"
|
"Custom"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"StartSloResponseParams": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"url"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"url": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"StartSsoResponseParams": {
|
"StartSsoResponseParams": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
@import "bootstrap/scss/forms";
|
@import "bootstrap/scss/forms";
|
||||||
@import "bootstrap/scss/buttons";
|
@import "bootstrap/scss/buttons";
|
||||||
@import "bootstrap/scss/transitions";
|
@import "bootstrap/scss/transitions";
|
||||||
// @import "bootstrap/scss/dropdown";
|
@import "bootstrap/scss/dropdown";
|
||||||
// @import "bootstrap/scss/button-group";
|
// @import "bootstrap/scss/button-group";
|
||||||
// @import "bootstrap/scss/nav";
|
// @import "bootstrap/scss/nav";
|
||||||
// @import "bootstrap/scss/navbar";
|
// @import "bootstrap/scss/navbar";
|
||||||
|
|
|
@ -7,3 +7,4 @@ $pagination-active-bg: transparent;
|
||||||
$pagination-hover-bg: transparent;
|
$pagination-hover-bg: transparent;
|
||||||
$pagination-focus-bg: transparent;
|
$pagination-focus-bg: transparent;
|
||||||
$modal-header-border-color: transparent;
|
$modal-header-border-color: transparent;
|
||||||
|
$dropdown-link-hover-bg: transparent;
|
||||||
|
|
Loading…
Reference in a new issue