Skip to content

Commit d841bf6

Browse files
committed
feat: add XH auth plugin intgeration
1 parent 6258784 commit d841bf6

10 files changed

Lines changed: 913 additions & 111 deletions

File tree

Cargo.lock

Lines changed: 55 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ rust-version = "1.88"
1717
name = "oidc"
1818
path = "src/main.rs"
1919

20+
[[bin]]
21+
name = "xh-plugin-oidc"
22+
path = "src/bin/xh-plugin-oidc.rs"
23+
2024
[package.metadata.binstall]
2125
pkg-url = "{ repo }/releases/download/v{ version }/oidc-{ target }{ binary-ext }"
2226
pkg-fmt = "bin"
@@ -43,6 +47,7 @@ serde_yaml = "0.9"
4347
simplelog = "0.12"
4448
time = { version = "0.3", features = ["serde-well-known", "formatting"] }
4549
tokio = { version = "1.36", features = ["full"] }
50+
toml = "1.1.2"
4651
url = "2"
4752

4853
openssl = "0.10" # transient dependency, required for vendoring

README.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,35 @@ This also works with `curl`:
7979
curl http://example.com/api -H $(oidc token -H my-client)
8080
```
8181

82+
## XH integration
83+
84+
Use the `xh-plugin-oidc` binary with `xh` custom auth plugins:
85+
86+
```bash
87+
xh --auth-type=plugin:oidc --auth=my-client https://example.com/api
88+
```
89+
90+
The `xh-plugin-oidc` binary can also discover the client name from a local config file. Starting from
91+
the current directory, it walks up parent directories and searches for `.xh-auth-oidc.json`,
92+
`.xh-auth-oidc.yaml`, then `.xh-auth-oidc.toml`:
93+
94+
```toml
95+
client_name = "my-client"
96+
97+
[http]
98+
timeout = "60s"
99+
connect_timeout = "30s"
100+
min_tls_version = "1.2"
101+
disable_system_certificates = false
102+
additional_root_certificates = []
103+
```
104+
105+
Then the client name does not need to be passed to `xh`:
106+
107+
```bash
108+
xh --auth-type=plugin:oidc https://example.com/api
109+
```
110+
82111
## More examples
83112

84113
Create a public client from an initial refresh token. This can be useful if you have a frontend application, but no

src/bin/xh-plugin-oidc.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#![deny(clippy::unwrap_used)]
2+
#![deny(clippy::expect_used)]
3+
4+
use std::{
5+
io::{stdin, stdout},
6+
process::ExitCode,
7+
};
8+
9+
#[tokio::main]
10+
async fn main() -> ExitCode {
11+
match oidc_cli::plugin::run(stdin().lock(), stdout().lock()).await {
12+
Ok(()) => ExitCode::SUCCESS,
13+
Err(err) => {
14+
eprintln!("{err}");
15+
for (n, cause) in err.chain().enumerate().skip(1) {
16+
eprintln!(" {n}: {cause}");
17+
}
18+
ExitCode::FAILURE
19+
}
20+
}
21+
}

src/cmd/completion.rs

Lines changed: 0 additions & 65 deletions
This file was deleted.

src/cmd/mod.rs

Lines changed: 5 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,5 @@
1-
mod completion;
2-
mod create;
3-
mod delete;
4-
mod inspect;
5-
mod list;
6-
mod token;
7-
8-
use std::process::ExitCode;
9-
10-
#[derive(Debug, clap::Subcommand)]
11-
#[allow(clippy::large_enum_variant)]
12-
pub enum Command {
13-
Create(create::Create),
14-
Delete(delete::Delete),
15-
Token(token::GetToken),
16-
List(list::List),
17-
Inspect(inspect::Inspect),
18-
Completion(completion::GetCompletion),
19-
}
20-
21-
impl Command {
22-
pub async fn run(self) -> anyhow::Result<ExitCode> {
23-
match self {
24-
Self::Create(cmd) => cmd.run().await,
25-
Self::Delete(cmd) => cmd.run().await,
26-
Self::Token(cmd) => cmd.run().await,
27-
Self::List(cmd) => cmd.run().await,
28-
Self::Inspect(cmd) => cmd.run().await,
29-
Self::Completion(cmd) => cmd.run().await,
30-
}
31-
.map(|()| ExitCode::SUCCESS)
32-
}
33-
}
1+
pub mod create;
2+
pub mod delete;
3+
pub mod inspect;
4+
pub mod list;
5+
pub mod token;

src/http.rs

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,27 @@
11
use anyhow::Context;
22
use reqwest::{header, tls::Version};
3+
use serde::Deserialize;
34
use std::path::PathBuf;
45

56
const USER_AGENT: &str = concat!("OIDC-CLI/", env!("CARGO_PKG_VERSION"));
67

7-
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug, clap::ValueEnum)]
8+
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug, clap::ValueEnum, Deserialize)]
89
pub enum TlsVersion {
910
/// TLS 1.0
1011
#[value(name("1.0"))]
12+
#[serde(rename = "1.0")]
1113
Tls1_0,
1214
/// TLS 1.1
1315
#[value(name("1.1"))]
16+
#[serde(rename = "1.1")]
1417
Tls1_1,
1518
/// TLS 1.2
1619
#[value(name("1.2"))]
20+
#[serde(rename = "1.2")]
1721
Tls1_2,
1822
/// TLS 1.3
1923
#[value(name("1.3"))]
24+
#[serde(rename = "1.3")]
2025
Tls1_3,
2126
}
2227

@@ -32,8 +37,9 @@ impl From<TlsVersion> for Version {
3237
}
3338

3439
/// HTTP client options
35-
#[derive(Clone, Debug, PartialEq, Eq, clap::Args)]
40+
#[derive(Clone, Debug, PartialEq, Eq, clap::Args, Deserialize)]
3641
#[command(next_help_heading = "HTTP client options")]
42+
#[serde(default)]
3743
pub struct HttpOptions {
3844
/// Disable TLS validation (INSECURE!)
3945
#[arg(long)]
@@ -49,17 +55,64 @@ pub struct HttpOptions {
4955

5056
/// Connect timeout
5157
#[arg(long, default_value = "30s")]
58+
#[serde(
59+
default = "default::connect_timeout",
60+
deserialize_with = "deserialize_duration"
61+
)]
5262
pub connect_timeout: humantime::Duration,
5363

5464
/// Request timeout
5565
#[arg(long, default_value = "60s", short = 't')]
66+
#[serde(
67+
default = "default::timeout",
68+
deserialize_with = "deserialize_duration"
69+
)]
5670
pub timeout: humantime::Duration,
5771

5872
/// Minimum TLS version
5973
#[arg(long, value_enum, default_value_t = TlsVersion::Tls1_2)]
74+
#[serde(default = "default::min_tls_version")]
6075
pub min_tls_version: TlsVersion,
6176
}
6277

78+
impl Default for HttpOptions {
79+
fn default() -> Self {
80+
Self {
81+
tls_insecure: false,
82+
additional_root_certificates: Vec::new(),
83+
disable_system_certificates: false,
84+
connect_timeout: default::connect_timeout(),
85+
timeout: default::timeout(),
86+
min_tls_version: default::min_tls_version(),
87+
}
88+
}
89+
}
90+
91+
mod default {
92+
use super::TlsVersion;
93+
94+
pub(super) fn connect_timeout() -> humantime::Duration {
95+
std::time::Duration::from_secs(30).into()
96+
}
97+
98+
pub(super) fn timeout() -> humantime::Duration {
99+
std::time::Duration::from_secs(60).into()
100+
}
101+
102+
pub(super) fn min_tls_version() -> TlsVersion {
103+
TlsVersion::Tls1_2
104+
}
105+
}
106+
107+
fn deserialize_duration<'de, D>(deserializer: D) -> Result<humantime::Duration, D::Error>
108+
where
109+
D: serde::Deserializer<'de>,
110+
{
111+
String::deserialize(deserializer)?
112+
.parse()
113+
.map_err(serde::de::Error::custom)
114+
}
115+
63116
/// A common way to create an HTTP client
64117
pub async fn create_client(options: &HttpOptions) -> anyhow::Result<reqwest::Client> {
65118
let mut headers = header::HeaderMap::new();

src/lib.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#![deny(clippy::unwrap_used)]
2+
#![deny(clippy::expect_used)]
3+
4+
pub mod claims;
5+
pub mod cmd;
6+
pub mod config;
7+
pub mod http;
8+
pub mod oidc;
9+
pub mod plugin;
10+
pub mod server;
11+
pub mod utils;

0 commit comments

Comments
 (0)