pr_01m47d24b0e6n91zwymwxg0vpx/crates/sshd/src/api.rs

100 lines2,687 bytesCodeBlame
1//! Client for the g1t Worker's internal endpoints, which own all
2//! authentication and authorization decisions.
3
4use anyhow::{Context, Result};
5use serde::{Deserialize, Serialize};
6
7#[derive(Clone, Debug, Deserialize)]
8pub struct User {
9 pub id: u64,
10 pub username: String,
11}
12
13/// An Artifacts remote and a short-lived token scoped to one repo.
14#[derive(Debug, Deserialize)]
15pub struct Access {
16 pub remote: String,
17 pub token: String,
18}
19
20#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
21pub enum Service {
22 #[serde(rename = "git-upload-pack")]
23 UploadPack,
24 #[serde(rename = "git-receive-pack")]
25 ReceivePack,
26}
27
28impl Service {
29 pub fn as_str(self) -> &'static str {
30 match self {
31 Service::UploadPack => "git-upload-pack",
32 Service::ReceivePack => "git-receive-pack",
33 }
34 }
35}
36
37#[derive(Deserialize)]
38struct ErrorBody {
39 error: String,
40}
41
42pub struct Api {
43 base: String,
44 secret: String,
45 pub http: reqwest::Client,
46}
47
48impl Api {
49 pub fn new(base: String, secret: String) -> Self {
50 Self {
51 base,
52 secret,
53 http: reqwest::Client::new(),
54 }
55 }
56
57 /// The user who registered the key with this SHA-256 fingerprint.
58 pub async fn user_for_key(&self, fingerprint: &str) -> Result<Option<User>> {
59 let response = self
60 .http
61 .post(format!("{}/_internal/ssh/user", self.base))
62 .bearer_auth(&self.secret)
63 .json(&serde_json::json!({ "fingerprint": fingerprint }))
64 .send()
65 .await
66 .context("key lookup failed")?;
67 if response.status() == reqwest::StatusCode::NOT_FOUND {
68 return Ok(None);
69 }
70 Ok(Some(response.error_for_status()?.json().await?))
71 }
72
73 /// `Ok(Err(message))` is a refusal to show the user.
74 pub async fn access(
75 &self,
76 user: &User,
77 owner: &str,
78 repo: &str,
79 service: Service,
80 ) -> Result<Result<Access, String>> {
81 let response = self
82 .http
83 .post(format!("{}/_internal/ssh/access", self.base))
84 .bearer_auth(&self.secret)
85 .json(&serde_json::json!({
86 "user_id": user.id,
87 "owner": owner,
88 "repo": repo,
89 "service": service,
90 }))
91 .send()
92 .await
93 .context("access check failed")?;
94 if response.status().is_client_error() {
95 let body: ErrorBody = response.json().await?;
96 return Ok(Err(body.error));
97 }
98 Ok(Ok(response.error_for_status()?.json().await?))
99 }
100}