Skip to content

Commit 682bcc6

Browse files
committed
refactor: create synd_authn crate
1 parent 2d0dbaf commit 682bcc6

File tree

14 files changed

+99
-66
lines changed

14 files changed

+99
-66
lines changed

Cargo.lock

+16-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+2
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,11 @@ clap = { version = "4.4" }
2020
feed-rs = { version = "1.4" }
2121
futures-util = { version = "0.3.30" }
2222
graphql_client = { version = "0.13.0", default-features = false }
23+
http = { version = "0.2" } # request use 0.2
2324
moka = { version = "0.12.4", features = ["future"] }
2425
reqwest = { version = "0.11.23", default-features = false, features = ["rustls-tls", "json"] }
2526
serde = { version = "1", features = ["derive"] }
27+
serde_json = { version = "1.0.111" }
2628
tokio = { version = "1.35" }
2729
tracing = { version = "0.1.40" }
2830
tracing-subscriber = { version = "0.3.18", features = ["smallvec", "fmt", "ansi", "std", "env-filter", "time"], default-features = false }

crates/synd_authn/Cargo.toml

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
[package]
2+
authors.workspace = true
3+
categories.workspace = true
4+
description.workspace = true
5+
edition.workspace = true
6+
license.workspace = true
7+
name = "synd_authn"
8+
readme.workspace = true
9+
repository.workspace = true
10+
version = "0.1.0"
11+
12+
[dependencies]
13+
anyhow = { workspace = true }
14+
http = { workspace = true }
15+
http-serde-ext = "0.1"
16+
reqwest = { workspace = true }
17+
serde = { workspace = true, features = ["derive"] }
18+
serde_json = { workspace = true }
19+
tokio = { workspace = true, features = ["time"] }
20+
tracing = { workspace = true }

crates/synd_term/src/auth/github.rs crates/synd_authn/src/device_flow/github.rs

+22-24
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,38 @@
1-
use std::{io::Write, time::Duration};
1+
use std::{borrow::Cow, future::Future, time::Duration};
22

3-
use http::StatusCode;
3+
use http::{StatusCode, Uri};
44
use reqwest::Client;
55
use tracing::debug;
66

7-
use crate::{
8-
auth::device_flow::{
9-
DeviceAccessTokenErrorResponse, DeviceAccessTokenRequest, DeviceAccessTokenResponse,
10-
DeviceAuthorizationRequest, DeviceAuthorizationResponse,
11-
},
12-
config,
7+
use crate::device_flow::{
8+
DeviceAccessTokenErrorResponse, DeviceAccessTokenRequest, DeviceAccessTokenResponse,
9+
DeviceAuthorizationRequest, DeviceAuthorizationResponse,
1310
};
1411

12+
const USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"));
13+
1514
/// https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps#device-flow
1615
#[derive(Clone)]
1716
pub struct DeviceFlow {
1817
client: Client,
19-
client_id: &'static str,
18+
client_id: Cow<'static, str>,
2019
endpoint: Option<&'static str>,
2120
}
2221

2322
impl DeviceFlow {
2423
const DEVICE_AUTHORIZATION_ENDPOINT: &'static str = "https://github.com/login/device/code";
2524
const TOKEN_ENDPOINT: &'static str = "https://github.com/login/oauth/access_token";
2625

27-
pub fn new() -> Self {
26+
pub fn new(client_id: impl Into<Cow<'static, str>>) -> Self {
2827
let client = reqwest::ClientBuilder::new()
29-
.user_agent(config::USER_AGENT)
28+
.user_agent(USER_AGENT)
3029
.timeout(Duration::from_secs(5))
3130
.build()
3231
.unwrap();
3332

3433
Self {
3534
client,
36-
client_id: config::github::CLIENT_ID,
35+
client_id: client_id.into(),
3736
endpoint: None,
3837
}
3938
}
@@ -57,7 +56,7 @@ impl DeviceFlow {
5756
.post(self.endpoint.unwrap_or(Self::DEVICE_AUTHORIZATION_ENDPOINT))
5857
.header(http::header::ACCEPT, "application/json")
5958
.form(&DeviceAuthorizationRequest {
60-
client_id: self.client_id.into(),
59+
client_id: self.client_id.clone(),
6160
scope: scope.into(),
6261
})
6362
.send()
@@ -99,7 +98,10 @@ impl DeviceFlow {
9998
.client
10099
.post(Self::TOKEN_ENDPOINT)
101100
.header(http::header::ACCEPT, "application/json")
102-
.form(&DeviceAccessTokenRequest::new(&device_code, self.client_id))
101+
.form(&DeviceAccessTokenRequest::new(
102+
&device_code,
103+
self.client_id.as_ref(),
104+
))
103105
.send()
104106
.await?;
105107

@@ -128,10 +130,11 @@ impl DeviceFlow {
128130
}
129131

130132
#[tracing::instrument(skip_all)]
131-
pub async fn device_flow<W: Write>(
132-
self,
133-
writer: W,
134-
) -> anyhow::Result<DeviceAccessTokenResponse> {
133+
pub async fn device_flow<F, Fut>(self, callback: F) -> anyhow::Result<DeviceAccessTokenResponse>
134+
where
135+
F: FnOnce(Uri, String) -> Fut,
136+
Fut: Future<Output = ()>,
137+
{
135138
let DeviceAuthorizationResponse {
136139
device_code,
137140
user_code,
@@ -140,12 +143,7 @@ impl DeviceFlow {
140143
..
141144
} = self.device_authorize_request().await?;
142145

143-
let mut writer = writer;
144-
writeln!(&mut writer, "Open `{verification_uri}` on your browser")?;
145-
writeln!(&mut writer, "Enter CODE: `{user_code}`")?;
146-
147-
// attempt to open input screen in the browser
148-
open::that(verification_uri.to_string()).ok();
146+
callback(verification_uri, user_code).await;
149147

150148
self.pool_device_access_token(device_code, interval).await
151149
}

crates/synd_term/src/auth/device_flow.rs crates/synd_authn/src/device_flow/mod.rs

+4-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ use std::borrow::Cow;
33
use http::Uri;
44
use serde::{Deserialize, Serialize};
55

6+
pub mod github;
7+
68
/// https://datatracker.ietf.org/doc/html/rfc8628#section-3.1
79
#[derive(Serialize, Deserialize, Debug)]
810
pub struct DeviceAuthorizationRequest<'s> {
@@ -39,13 +41,13 @@ pub struct DeviceAccessTokenRequest<'s> {
3941
grant_type: &'static str,
4042
/// The device verification code, "device_code" from the device authorization response
4143
device_code: &'s str,
42-
client_id: &'static str,
44+
client_id: &'s str,
4345
}
4446

4547
impl<'s> DeviceAccessTokenRequest<'s> {
4648
const GRANT_TYPE: &'static str = "urn:ietf:params:oauth:grant-type:device_code";
4749

48-
pub fn new(device_code: &'s str, client_id: &'static str) -> Self {
50+
pub fn new(device_code: &'s str, client_id: &'s str) -> Self {
4951
Self {
5052
grant_type: Self::GRANT_TYPE,
5153
device_code,

crates/synd_authn/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pub mod device_flow;

crates/synd_term/Cargo.toml

+13-15
Original file line numberDiff line numberDiff line change
@@ -12,25 +12,23 @@ name = "synd"
1212
path = "src/main.rs"
1313

1414
[dependencies]
15-
anyhow = { workspace = true }
16-
chrono = { workspace = true }
17-
clap = { workspace = true, features = ["derive", "string"] }
18-
crossterm = { version = "0.27.0", features = ["event-stream"] }
19-
directories = "5.0.1"
20-
edit = "0.1.5"
21-
futures-util = "0.3.30"
22-
graphql_client = { workspace = true }
23-
# reqwest use 0.2
24-
html2text = { version = "0.12" }
25-
http = "0.2"
26-
http-serde-ext = "0.1"
27-
open = "5.0.1"
28-
# Use latest api
15+
synd_authn = { path = "../synd_authn", version = "0.1.0" }
16+
synd_feed = { path = "../synd_feed", version = "0.1.0" }
17+
18+
anyhow = { workspace = true }
19+
chrono = { workspace = true }
20+
clap = { workspace = true, features = ["derive", "string"] }
21+
crossterm = { version = "0.27.0", features = ["event-stream"] }
22+
directories = "5.0.1"
23+
edit = "0.1.5"
24+
futures-util = "0.3.30"
25+
graphql_client = { workspace = true }
26+
html2text = { version = "0.12" }
27+
open = "5.0.1"
2928
ratatui = { git = "https://github.com/ratatui-org/ratatui.git", branch = "main" }
3029
reqwest = { workspace = true }
3130
serde = { workspace = true, features = ["derive"] }
3231
serde_json = "1.0.111"
33-
synd_feed = { path = "../synd_feed", version = "0.1.0" }
3432
tokio = { workspace = true, features = ["macros", "rt-multi-thread", "sync", "time"] }
3533
tracing = { workspace = true }
3634
tracing-appender = "0.2.3"

crates/synd_term/src/application/mod.rs

+8-8
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,16 @@ use std::{pin::Pin, time::Duration};
33
use crossterm::event::{Event as CrosstermEvent, KeyCode, KeyEvent, KeyEventKind};
44
use futures_util::{FutureExt, Stream, StreamExt};
55
use ratatui::widgets::Widget;
6+
use synd_authn::device_flow::{
7+
github::DeviceFlow, DeviceAccessTokenResponse, DeviceAuthorizationResponse,
8+
};
69
use tokio::time::{Instant, Sleep};
710

811
use crate::{
9-
auth::{
10-
self,
11-
device_flow::{DeviceAccessTokenResponse, DeviceAuthorizationResponse},
12-
github::DeviceFlow,
13-
AuthenticationProvider, Credential,
14-
},
12+
auth::{self, AuthenticationProvider, Credential},
1513
client::Client,
1614
command::Command,
15+
config,
1716
job::Jobs,
1817
terminal::Terminal,
1918
ui::{
@@ -45,7 +44,7 @@ impl Default for Config {
4544
fn default() -> Self {
4645
Self {
4746
idle_timer_interval: Duration::from_secs(250),
48-
github_device_flow: DeviceFlow::new(),
47+
github_device_flow: DeviceFlow::new(config::github::CLIENT_ID),
4948
}
5049
}
5150
}
@@ -460,9 +459,10 @@ impl Application {
460459
// attempt to open input screen in the browser
461460
open::that(device_authorization.verification_uri.to_string()).ok();
462461

462+
let device_flow = self.config.github_device_flow.clone();
463463
let fut = async move {
464464
// TODO: error handling
465-
let res = auth::github::DeviceFlow::new()
465+
let res = device_flow
466466
.pool_device_access_token(
467467
device_authorization.device_code,
468468
device_authorization.interval,

crates/synd_term/src/auth/mod.rs

-3
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,6 @@ use tracing::debug;
55

66
use crate::config;
77

8-
pub mod device_flow;
9-
pub mod github;
10-
118
#[derive(Debug, Clone, Copy)]
129
pub enum AuthenticationProvider {
1310
Github,

crates/synd_term/src/command.rs

+2-4
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
use std::fmt::Display;
2+
use synd_authn::device_flow::{DeviceAccessTokenResponse, DeviceAuthorizationResponse};
23

34
use crate::{
45
application::Direction,
5-
auth::{
6-
device_flow::{DeviceAccessTokenResponse, DeviceAuthorizationResponse},
7-
AuthenticationProvider,
8-
},
6+
auth::AuthenticationProvider,
97
client::{payload, query::subscription::SubscriptionOutput},
108
types::Feed,
119
};

crates/synd_term/src/ui/components/authentication.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@ use ratatui::{
77
Widget,
88
},
99
};
10+
use synd_authn::device_flow::DeviceAuthorizationResponse;
1011

1112
use crate::{
12-
auth::{device_flow::DeviceAuthorizationResponse, AuthenticationProvider},
13+
auth::AuthenticationProvider,
1314
ui::{self, extension::RectExt, Context},
1415
};
1516

crates/synd_term/tests/integration.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ mod test {
1010
style::{Modifier, Style},
1111
};
1212
use serial_test::file_serial;
13+
use synd_authn::device_flow::github::DeviceFlow;
1314
use synd_term::{
1415
application::{Application, Config},
15-
auth::github::DeviceFlow,
1616
client::Client,
1717
ui::theme::Theme,
1818
};
@@ -42,7 +42,7 @@ mod test {
4242
let client = Client::new(endpoint).unwrap();
4343
let config = Config {
4444
idle_timer_interval: Duration::from_millis(2000),
45-
github_device_flow: DeviceFlow::new()
45+
github_device_flow: DeviceFlow::new("dummy")
4646
.with_endpoint("http://localhost:6000/github/login/device/code"),
4747
};
4848
// or mpsc and tokio_stream ReceiverStream

crates/synd_test/Cargo.toml

+6-4
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ repository.workspace = true
1010
version = "0.1.0"
1111

1212
[dependencies]
13-
anyhow = { workspace = true }
14-
axum = { workspace = true }
15-
synd_term = { path = "../synd_term" }
16-
tokio = { workspace = true, features = ["rt-multi-thread", "net"] }
13+
synd_authn = { path = "../synd_authn" }
14+
synd_term = { path = "../synd_term" }
15+
16+
anyhow = { workspace = true }
17+
axum = { workspace = true }
18+
tokio = { workspace = true, features = ["rt-multi-thread", "net"] }

crates/synd_test/src/oauth.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use axum::{http::StatusCode, routing::post, Form, Json, Router};
2-
use synd_term::auth::device_flow::{DeviceAuthorizationRequest, DeviceAuthorizationResponse};
2+
use synd_authn::device_flow::{DeviceAuthorizationRequest, DeviceAuthorizationResponse};
33
use tokio::net::TcpListener;
44

55
async fn device_authorization(

0 commit comments

Comments
 (0)