fix(git): authorize html fragments by session instead of basic auth
ZhenYi committed
This change is very large, so some diffs are not shown. Clone the repository to see the full change.
Modified lib/auth/src/openapi.rs lib/auth/src/openapi.rs +0 −1 View lib/auth/src/openapi.rs
@@ -98,7 +98,6 @@
9898 handlers::two_factor::TotpCodeCmd,
9999 handlers::two_factor::RegenerateRecoveryCodesCmd,
100100 handlers::two_factor::RecoveryCodesResp,
101 models::users::access_scope::AccessKeyScope,
102101 handlers::sessions::SessionDto,
103102 handlers::sessions::SessionKind,
104103 handlers::sessions::CountResp,
Modified lib/git/src/handler/html/badge.rs lib/git/src/handler/html/badge.rs +3 −3 View lib/git/src/handler/html/badge.rs
@@ -3,7 +3,7 @@
33
44use crate::handler::html::{HtmlRepoPath, archived_class, format_size, resolve, response_html};
55use crate::service::GitService;
6use crate::share::access::BasicAuth;
6use ::session::Session;
77
88#[utoipa::path(
99 get,
@@ -20,9 +20,9 @@
2020pub async fn render_badge(
2121 State(service): State<GitService>,
2222 Path(path): Path<HtmlRepoPath>,
23 auth: BasicAuth,
23 session: Session,
2424) -> Response {
25 let ctx = match resolve(service, path, auth).await {
25 let ctx = match resolve(service, path, session).await {
2626 Ok(c) => c,
2727 Err(resp) => return resp,
2828 };
Modified lib/git/src/handler/html/blame.rs lib/git/src/handler/html/blame.rs +3 −3 View lib/git/src/handler/html/blame.rs
@@ -8,7 +8,7 @@
88 HtmlRepoPath, archived_class, error, resolve_view, response_html, short_sha,
99};
1010use crate::service::GitService;
11use crate::share::access::BasicAuth;
11use ::session::Session;
1212
1313#[derive(Debug, Clone, serde::Deserialize, utoipa::IntoParams)]
1414pub struct BlameQuery {
@@ -34,9 +34,9 @@
3434 State(service): State<GitService>,
3535 Path(path): Path<HtmlRepoPath>,
3636 Query(q): Query<BlameQuery>,
37 auth: BasicAuth,
37 session: Session,
3838) -> Response {
39 let view = match resolve_view(service.clone(), path, auth, q.revision, q.path, true).await {
39 let view = match resolve_view(service.clone(), path, session, q.revision, q.path, true).await {
4040 Ok(v) => v,
4141 Err(resp) => return resp,
4242 };
Modified lib/git/src/handler/html/blob_content.rs lib/git/src/handler/html/blob_content.rs +3 −3 View lib/git/src/handler/html/blob_content.rs
@@ -6,7 +6,7 @@
66 HtmlRepoPath, archived_class, error, format_size, resolve_view, response_html,
77};
88use crate::service::GitService;
9use crate::share::access::BasicAuth;
9use ::session::Session;
1010
1111#[derive(Debug, Clone, serde::Deserialize, utoipa::IntoParams)]
1212pub struct BlobContentQuery {
@@ -32,9 +32,9 @@
3232 State(service): State<GitService>,
3333 Path(path): Path<HtmlRepoPath>,
3434 Query(q): Query<BlobContentQuery>,
35 auth: BasicAuth,
35 session: Session,
3636) -> Response {
37 let view = match resolve_view(service.clone(), path, auth, q.revision, q.path, true).await {
37 let view = match resolve_view(service.clone(), path, session, q.revision, q.path, true).await {
3838 Ok(v) => v,
3939 Err(resp) => return resp,
4040 };
Modified lib/git/src/handler/html/blob_header.rs lib/git/src/handler/html/blob_header.rs +3 −3 View lib/git/src/handler/html/blob_header.rs
@@ -3,7 +3,7 @@
33
44use crate::handler::html::{HtmlRepoPath, archived_class, resolve_view, response_html};
55use crate::service::GitService;
6use crate::share::access::BasicAuth;
6use ::session::Session;
77
88#[derive(Debug, Clone, serde::Deserialize, utoipa::IntoParams)]
99pub struct BlobHeaderQuery {
@@ -29,9 +29,9 @@
2929 State(service): State<GitService>,
3030 Path(path): Path<HtmlRepoPath>,
3131 Query(q): Query<BlobHeaderQuery>,
32 auth: BasicAuth,
32 session: Session,
3333) -> Response {
34 let view = match resolve_view(service.clone(), path, auth, q.revision, q.path, true).await {
34 let view = match resolve_view(service.clone(), path, session, q.revision, q.path, true).await {
3535 Ok(v) => v,
3636 Err(resp) => return resp,
3737 };
Modified lib/git/src/handler/html/blob_pages.rs lib/git/src/handler/html/blob_pages.rs +3 −3 View lib/git/src/handler/html/blob_pages.rs
@@ -3,7 +3,7 @@
33
44use crate::handler::html::{HtmlRepoPath, error, resolve_view};
55use crate::service::{GitService, pdf};
6use crate::share::access::BasicAuth;
6use ::session::Session;
77
88#[derive(Debug, Clone, serde::Deserialize, utoipa::IntoParams)]
99pub struct BlobPagesQuery {
@@ -41,9 +41,9 @@
4141 State(service): State<GitService>,
4242 Path(path): Path<HtmlRepoPath>,
4343 Query(q): Query<BlobPagesQuery>,
44 auth: BasicAuth,
44 session: Session,
4545) -> Response {
46 let view = match resolve_view(service.clone(), path, auth, q.revision, q.path, true).await {
46 let view = match resolve_view(service.clone(), path, session, q.revision, q.path, true).await {
4747 Ok(v) => v,
4848 Err(resp) => return resp,
4949 };
Modified lib/git/src/handler/html/breadcrumbs.rs lib/git/src/handler/html/breadcrumbs.rs +3 −3 View lib/git/src/handler/html/breadcrumbs.rs
@@ -3,7 +3,7 @@
33
44use crate::handler::html::{HtmlRepoPath, archived_class, error, resolve, response_html};
55use crate::service::GitService;
6use crate::share::access::BasicAuth;
6use ::session::Session;
77use crate::share::validate;
88
99#[derive(Debug, Clone, serde::Deserialize, utoipa::IntoParams)]
@@ -30,9 +30,9 @@
3030 State(service): State<GitService>,
3131 Path(path): Path<HtmlRepoPath>,
3232 Query(q): Query<BreadcrumbsQuery>,
33 auth: BasicAuth,
33 session: Session,
3434) -> Response {
35 let ctx = match resolve(service, path, auth).await {
35 let ctx = match resolve(service, path, session).await {
3636 Ok(c) => c,
3737 Err(resp) => return resp,
3838 };
Modified lib/git/src/handler/html/clone_buttons.rs lib/git/src/handler/html/clone_buttons.rs +3 −3 View lib/git/src/handler/html/clone_buttons.rs
@@ -3,7 +3,7 @@
33
44use crate::handler::html::{HtmlRepoPath, clone_https_url, clone_ssh_url, resolve, response_html};
55use crate::service::GitService;
6use crate::share::access::BasicAuth;
6use ::session::Session;
77
88#[utoipa::path(
99 get,
@@ -20,9 +20,9 @@
2020pub async fn render_clone_buttons(
2121 State(service): State<GitService>,
2222 Path(path): Path<HtmlRepoPath>,
23 auth: BasicAuth,
23 session: Session,
2424) -> Response {
25 let ctx = match resolve(service.clone(), path.clone(), auth).await {
25 let ctx = match resolve(service.clone(), path.clone(), session).await {
2626 Ok(c) => c,
2727 Err(resp) => return resp,
2828 };
Modified lib/git/src/handler/html/commit_diff.rs lib/git/src/handler/html/commit_diff.rs +3 −3 View lib/git/src/handler/html/commit_diff.rs
@@ -6,7 +6,7 @@
66};
77use crate::service::GitService;
88use crate::service::diffs::ChangedPathDiffDto;
9use crate::share::access::BasicAuth;
9use ::session::Session;
1010
1111const MAX_FILES: usize = 50;
1212const MAX_LINES_PER_FILE: usize = 2000;
@@ -35,9 +35,9 @@
3535 State(service): State<GitService>,
3636 Path(path): Path<HtmlRepoPath>,
3737 Query(q): Query<CommitDiffQuery>,
38 auth: BasicAuth,
38 session: Session,
3939) -> Response {
40 let ctx = match resolve(service.clone(), path, auth).await {
40 let ctx = match resolve(service.clone(), path, session).await {
4141 Ok(c) => c,
4242 Err(resp) => return resp,
4343 };
Modified lib/git/src/handler/html/commits.rs lib/git/src/handler/html/commits.rs +3 −3 View lib/git/src/handler/html/commits.rs
@@ -9,7 +9,7 @@
99};
1010use crate::service::GitService;
1111use crate::service::commits::ListCommitsCmd;
12use crate::share::access::BasicAuth;
12use ::session::Session;
1313
1414#[derive(Debug, Clone, serde::Deserialize, utoipa::IntoParams)]
1515pub struct CommitsQuery {
@@ -39,9 +39,9 @@
3939 State(service): State<GitService>,
4040 Path(path): Path<HtmlRepoPath>,
4141 Query(q): Query<CommitsQuery>,
42 auth: BasicAuth,
42 session: Session,
4343) -> Response {
44 let view = match resolve_view(service.clone(), path, auth, q.revision, q.path, false).await {
44 let view = match resolve_view(service.clone(), path, session, q.revision, q.path, false).await {
4545 Ok(v) => v,
4646 Err(resp) => return resp,
4747 };
Modified lib/git/src/handler/html/error.rs lib/git/src/handler/html/error.rs +0 −6 View lib/git/src/handler/html/error.rs
@@ -30,11 +30,5 @@
3030 );
3131 resp.headers_mut()
3232 .insert(actix_web::http::header::VARY, "Cookie".parse().unwrap());
33 if status == StatusCode::UNAUTHORIZED {
34 resp.headers_mut().insert(
35 actix_web::http::header::WWW_AUTHENTICATE,
36 "Basic realm=\"gitdata\"".parse().unwrap(),
37 );
38 }
3933 resp
4034}
Modified lib/git/src/handler/html/header.rs lib/git/src/handler/html/header.rs +3 −3 View lib/git/src/handler/html/header.rs
@@ -3,7 +3,7 @@
33
44use crate::handler::html::{HtmlRepoPath, archived_class, resolve, response_html};
55use crate::service::GitService;
6use crate::share::access::BasicAuth;
6use ::session::Session;
77
88#[utoipa::path(
99 get,
@@ -20,9 +20,9 @@
2020pub async fn render_header(
2121 State(service): State<GitService>,
2222 Path(path): Path<HtmlRepoPath>,
23 auth: BasicAuth,
23 session: Session,
2424) -> Response {
25 let ctx = match resolve(service, path, auth).await {
25 let ctx = match resolve(service, path, session).await {
2626 Ok(c) => c,
2727 Err(resp) => return resp,
2828 };
Modified lib/git/src/handler/html/languages.rs lib/git/src/handler/html/languages.rs +3 −3 View lib/git/src/handler/html/languages.rs
@@ -3,7 +3,7 @@
33
44use crate::handler::html::{HtmlRepoPath, archived_class, resolve, response_html};
55use crate::service::GitService;
6use crate::share::access::BasicAuth;
6use ::session::Session;
77
88#[utoipa::path(
99 get,
@@ -20,9 +20,9 @@
2020pub async fn render_languages(
2121 State(service): State<GitService>,
2222 Path(path): Path<HtmlRepoPath>,
23 auth: BasicAuth,
23 session: Session,
2424) -> Response {
25 let ctx = match resolve(service.clone(), path, auth).await {
25 let ctx = match resolve(service.clone(), path, session).await {
2626 Ok(c) => c,
2727 Err(resp) => return resp,
2828 };
Modified lib/git/src/handler/html/last_commit.rs lib/git/src/handler/html/last_commit.rs +3 −3 View lib/git/src/handler/html/last_commit.rs
@@ -6,7 +6,7 @@
66 HtmlRepoPath, archived_class, error, first_line, resolve, response_html, short_sha,
77};
88use crate::service::GitService;
9use crate::share::access::BasicAuth;
9use ::session::Session;
1010use crate::share::validate;
1111
1212#[derive(Debug, Clone, serde::Deserialize, utoipa::IntoParams)]
@@ -33,9 +33,9 @@
3333 State(service): State<GitService>,
3434 Path(path): Path<HtmlRepoPath>,
3535 Query(q): Query<LastCommitQuery>,
36 auth: BasicAuth,
36 session: Session,
3737) -> Response {
38 let ctx = match resolve(service.clone(), path.clone(), auth).await {
38 let ctx = match resolve(service.clone(), path.clone(), session).await {
3939 Ok(c) => c,
4040 Err(resp) => return resp,
4141 };
Modified lib/git/src/handler/html/mod.rs lib/git/src/handler/html/mod.rs +18 −11 View lib/git/src/handler/html/mod.rs
@@ -7,9 +7,10 @@
77use tracing::warn;
88
99use crate::context::facade::GitContext;
10use crate::errors::ApiError;
11use crate::handler::ensure_read_access;
1012use crate::service::GitService;
1113use crate::service::repos::RepoDto;
12use crate::share::access::{BasicAuth, allow_repo_read};
1314use crate::share::errors::GitError;
1415
1516pub mod badge;
@@ -43,6 +44,17 @@
4344const MAX_SUBJECT_LEN: usize = 200;
4445const MAX_REVISION_LEN: usize = 256;
4546
47fn error_status(error: &ApiError) -> u16 {
48 match error {
49 ApiError::BadRequest(_) => 400,
50 ApiError::Unauthorized(_) => 401,
51 ApiError::Forbidden(_) => 403,
52 ApiError::NotFound(_) => 404,
53 ApiError::Conflict(_) => 409,
54 ApiError::Internal(_) => 500,
55 }
56}
57
4658#[derive(Clone)]
4759pub struct HtmlCtx {
4860 pub service: GitService,
@@ -135,7 +147,7 @@
135147pub async fn resolve(
136148 service: GitService,
137149 path: HtmlRepoPath,
138 auth: BasicAuth,
150 session: ::session::Session,
139151) -> Result<HtmlCtx, Response> {
140152 if path.space_name.chars().count() > MAX_NAME_LEN
141153 || path.repo_name.chars().count() > MAX_NAME_LEN
@@ -153,14 +165,9 @@
153165 return Err(html_err(500, "internal"));
154166 }
155167 };
156 let uid = match allow_repo_read(&service, &auth, &ctx).await {
168 let uid = match ensure_read_access(&service, &session, &ctx).await {
157169 Ok(uid) => uid,
158 Err(GitError::Unauthorized) => return Err(html_err(401, "auth required")),
159 Err(GitError::Forbidden) => return Err(html_err(403, "forbidden")),
160 Err(e) => {
161 warn!(error = %e, "html auth internal");
162 return Err(html_err(500, "internal"));
163 }
170 Err(error) => return Err(html_err(error_status(&error), "")),
164171 };
165172 let dto = match service.get_repo_for_html(ctx.clone()).await {
166173 Ok(d) => d,
@@ -191,12 +198,12 @@
191198pub async fn resolve_view(
192199 service: GitService,
193200 path: HtmlRepoPath,
194 auth: BasicAuth,
201 session: ::session::Session,
195202 revision: Option<String>,
196203 raw_path: Option<String>,
197204 require_path: bool,
198205) -> Result<HtmlView, Response> {
199 let ctx = resolve(service, path, auth).await?;
206 let ctx = resolve(service, path, session).await?;
200207 let revision = revision
201208 .as_deref()
202209 .unwrap_or(ctx.dto.default_branch.as_str())
Modified lib/git/src/handler/html/quickstart.rs lib/git/src/handler/html/quickstart.rs +3 −3 View lib/git/src/handler/html/quickstart.rs
@@ -3,7 +3,7 @@
33
44use crate::handler::html::{HtmlRepoPath, clone_https_url, clone_ssh_url, resolve, response_html};
55use crate::service::GitService;
6use crate::share::access::BasicAuth;
6use ::session::Session;
77
88#[utoipa::path(
99 get,
@@ -20,9 +20,9 @@
2020pub async fn render_quickstart(
2121 State(service): State<GitService>,
2222 Path(path): Path<HtmlRepoPath>,
23 auth: BasicAuth,
23 session: Session,
2424) -> Response {
25 let ctx = match resolve(service.clone(), path.clone(), auth).await {
25 let ctx = match resolve(service.clone(), path.clone(), session).await {
2626 Ok(c) => c,
2727 Err(resp) => return resp,
2828 };
Modified lib/git/src/handler/html/readme.rs lib/git/src/handler/html/readme.rs +3 −3 View lib/git/src/handler/html/readme.rs
@@ -6,7 +6,7 @@
66 HtmlRepoPath, archived_class, error, format_size, resolve_view, response_html,
77};
88use crate::service::GitService;
9use crate::share::access::BasicAuth;
9use ::session::Session;
1010
1111const README_CANDIDATES: &[&str] = &[
1212 "README.md",
@@ -45,9 +45,9 @@
4545 State(service): State<GitService>,
4646 Path(path): Path<HtmlRepoPath>,
4747 Query(q): Query<ReadmeQuery>,
48 auth: BasicAuth,
48 session: Session,
4949) -> Response {
50 let view = match resolve_view(service.clone(), path, auth, q.revision, q.path, false).await {
50 let view = match resolve_view(service.clone(), path, session, q.revision, q.path, false).await {
5151 Ok(v) => v,
5252 Err(resp) => return resp,
5353 };
Modified lib/git/src/handler/html/refs_dropdown.rs lib/git/src/handler/html/refs_dropdown.rs +3 −3 View lib/git/src/handler/html/refs_dropdown.rs
@@ -3,7 +3,7 @@
33
44use crate::handler::html::{HtmlRepoPath, archived_class, resolve, response_html};
55use crate::service::GitService;
6use crate::share::access::BasicAuth;
6use ::session::Session;
77use crate::share::validate;
88
99#[derive(Debug, Clone, serde::Deserialize, utoipa::IntoParams)]
@@ -28,9 +28,9 @@
2828 State(service): State<GitService>,
2929 Path(path): Path<HtmlRepoPath>,
3030 Query(q): Query<RefsDropdownQuery>,
31 auth: BasicAuth,
31 session: Session,
3232) -> Response {
33 let ctx = match resolve(service.clone(), path, auth).await {
33 let ctx = match resolve(service.clone(), path, session).await {
3434 Ok(c) => c,
3535 Err(resp) => return resp,
3636 };
Modified lib/git/src/handler/html/refs_summary.rs lib/git/src/handler/html/refs_summary.rs +3 −3 View lib/git/src/handler/html/refs_summary.rs
@@ -3,7 +3,7 @@
33
44use crate::handler::html::{HtmlRepoPath, archived_class, resolve, response_html};
55use crate::service::GitService;
6use crate::share::access::BasicAuth;
6use ::session::Session;
77
88#[utoipa::path(
99 get,
@@ -20,9 +20,9 @@
2020pub async fn render_refs_summary(
2121 State(service): State<GitService>,
2222 Path(path): Path<HtmlRepoPath>,
23 auth: BasicAuth,
23 session: Session,
2424) -> Response {
25 let ctx = match resolve(service, path, auth).await {
25 let ctx = match resolve(service, path, session).await {
2626 Ok(c) => c,
2727 Err(resp) => return resp,
2828 };
Modified lib/git/src/handler/html/star_button.rs lib/git/src/handler/html/star_button.rs +3 −3 View lib/git/src/handler/html/star_button.rs
@@ -3,7 +3,7 @@
33
44use crate::handler::html::{HtmlRepoPath, archived_class, resolve, response_html};
55use crate::service::GitService;
6use crate::share::access::BasicAuth;
6use ::session::Session;
77
88#[utoipa::path(
99 get,
@@ -20,9 +20,9 @@
2020pub async fn render_star_button(
2121 State(service): State<GitService>,
2222 Path(path): Path<HtmlRepoPath>,
23 auth: BasicAuth,
23 session: Session,
2424) -> Response {
25 let ctx = match resolve(service.clone(), path, auth).await {
25 let ctx = match resolve(service.clone(), path, session).await {
2626 Ok(c) => c,
2727 Err(resp) => return resp,
2828 };
Modified lib/git/src/handler/html/tree.rs lib/git/src/handler/html/tree.rs +3 −3 View lib/git/src/handler/html/tree.rs
@@ -8,7 +8,7 @@
88 resolve_view, response_html, short_sha,
99};
1010use crate::service::GitService;
11use crate::share::access::BasicAuth;
11use ::session::Session;
1212
1313#[derive(Debug, Clone, serde::Deserialize, utoipa::IntoParams)]
1414pub struct TreeQuery {
@@ -36,9 +36,9 @@
3636 State(service): State<GitService>,
3737 Path(path): Path<HtmlRepoPath>,
3838 Query(q): Query<TreeQuery>,
39 auth: BasicAuth,
39 session: Session,
4040) -> Response {
41 let view = match resolve_view(service.clone(), path, auth, q.revision, q.path, false).await {
41 let view = match resolve_view(service.clone(), path, session, q.revision, q.path, false).await {
4242 Ok(v) => v,
4343 Err(resp) => return resp,
4444 };
Modified lib/git/src/handler/html/tree_row.rs lib/git/src/handler/html/tree_row.rs +3 −3 View lib/git/src/handler/html/tree_row.rs
@@ -7,7 +7,7 @@
77 short_sha,
88};
99use crate::service::GitService;
10use crate::share::access::BasicAuth;
10use ::session::Session;
1111
1212#[derive(Debug, Clone, serde::Deserialize, utoipa::IntoParams)]
1313pub struct TreeRowQuery {
@@ -35,9 +35,9 @@
3535 State(service): State<GitService>,
3636 Path(path): Path<HtmlRepoPath>,
3737 Query(q): Query<TreeRowQuery>,
38 auth: BasicAuth,
38 session: Session,
3939) -> Response {
40 let view = match resolve_view(service.clone(), path, auth, q.revision, q.path, false).await {
40 let view = match resolve_view(service.clone(), path, session, q.revision, q.path, false).await {
4141 Ok(v) => v,
4242 Err(resp) => return resp,
4343 };
Modified lib/git/src/handler/html/watch_button.rs lib/git/src/handler/html/watch_button.rs +3 −3 View lib/git/src/handler/html/watch_button.rs
@@ -4,7 +4,7 @@
44
55use crate::handler::html::{HtmlRepoPath, archived_class, resolve, response_html};
66use crate::service::GitService;
7use crate::share::access::BasicAuth;
7use ::session::Session;
88
99#[utoipa::path(
1010 get,
@@ -21,9 +21,9 @@
2121pub async fn render_watch_button(
2222 State(service): State<GitService>,
2323 Path(path): Path<HtmlRepoPath>,
24 auth: BasicAuth,
24 session: Session,
2525) -> Response {
26 let ctx = match resolve(service.clone(), path, auth).await {
26 let ctx = match resolve(service.clone(), path, session).await {
2727 Ok(c) => c,
2828 Err(resp) => return resp,
2929 };
Modified lib/git/src/share/access.rs lib/git/src/share/access.rs +0 −113 View lib/git/src/share/access.rs
@@ -1,121 +1,8 @@
1use std::future::Ready;
2
3use actix_web::dev::Payload;
4use actix_web::http::header;
5use actix_web::{FromRequest, HttpRequest};
6use base64::Engine as _;
7use models::users::access_scope::AccessKeyScope;
81use uuid::Uuid;
92
103use crate::context::facade::GitContext;
114use crate::service::GitService;
125use crate::share::errors::GitError;
13
14#[derive(Debug, Clone)]
15pub struct BasicAuth {
16 pub token: Option<String>,
17}
18
19impl FromRequest for BasicAuth {
20 type Error = GitError;
21 type Future = Ready<Result<Self, Self::Error>>;
22
23 fn from_request(request: &HttpRequest, _payload: &mut Payload) -> Self::Future {
24 std::future::ready(parse_basic_auth(request))
25 }
26}
27
28fn parse_basic_auth(request: &HttpRequest) -> Result<BasicAuth, GitError> {
29 let Some(value) = request.headers().get(header::AUTHORIZATION) else {
30 return Ok(BasicAuth { token: None });
31 };
32 let raw = value
33 .to_str()
34 .map_err(|_| GitError::InvalidArgument("malformed authorization header".into()))?;
35 let Some(b64) = raw.strip_prefix("Basic ") else {
36 return Ok(BasicAuth { token: None });
37 };
38 let decoded = base64::engine::general_purpose::STANDARD
39 .decode(b64)
40 .map_err(|_| GitError::InvalidArgument("malformed basic credentials".into()))?;
41 let creds = String::from_utf8(decoded)
42 .map_err(|_| GitError::InvalidArgument("malformed basic credentials".into()))?;
43 let token = creds.split_once(':').map(|(_, pass)| pass.to_string());
44 Ok(BasicAuth { token })
45}
46
47pub async fn verify_access_key(
48 _svc: &GitService,
49 _token: &str,
50) -> Result<Option<(i64, AccessKeyScope)>, GitError> {
51 Ok(None)
52}
53
54/// HTTP write-path auth: token must be valid AND the resolved `uid` must
55/// have write membership on the resolved `ctx` repo. Use everywhere the
56/// HTTP path accepts mutating requests.
57pub async fn require_writer(
58 svc: &GitService,
59 auth: &BasicAuth,
60 ctx: &GitContext,
61) -> Result<i64, GitError> {
62 let uid = resolve_caller_for_write(svc, auth).await?;
63 authorize_repo_access(svc, uid, ctx, true, false).await?;
64 Ok(uid)
65}
66
67/// HTTP read-path auth: `Ok(None)` only for anonymous reads on public
68/// repos; otherwise returns `Unauthorized` / `Forbidden` / `Some(uid)`.
69pub async fn allow_repo_read(
70 svc: &GitService,
71 auth: &BasicAuth,
72 ctx: &GitContext,
73) -> Result<Option<i64>, GitError> {
74 let Some(token) = &auth.token else {
75 if ctx.model.is_private {
76 return Err(GitError::Unauthorized);
77 }
78 return Ok(None);
79 };
80 let (uid, _) = verify_access_key(svc, token)
81 .await?
82 .ok_or(GitError::Unauthorized)?;
83 authorize_repo_access(svc, uid, ctx, false, false).await?;
84 Ok(Some(uid))
85}
86
87/// Admin-only HTTP auth: caller must hold a valid write-scope token AND
88/// be the space owner or an Admin collaborator. Use for LFS force-unlock
89/// and any action spec'd as admin-only.
90pub async fn require_repo_admin(
91 svc: &GitService,
92 auth: &BasicAuth,
93 ctx: &GitContext,
94) -> Result<i64, GitError> {
95 if auth
96 .token
97 .as_deref()
98 .is_some_and(|token| token.starts_with("ci_"))
99 {
100 return Err(GitError::Forbidden);
101 }
102 let uid = resolve_caller_for_write(svc, auth).await?;
103 authorize_repo_access(svc, uid, ctx, true, true).await?;
104 Ok(uid)
105}
106
107async fn resolve_caller_for_write(svc: &GitService, auth: &BasicAuth) -> Result<i64, GitError> {
108 let Some(token) = &auth.token else {
109 return Err(GitError::Unauthorized);
110 };
111 let (uid, scope) = verify_access_key(svc, token)
112 .await?
113 .ok_or(GitError::Unauthorized)?;
114 if !scope.allows(true) {
115 return Err(GitError::Forbidden);
116 }
117 Ok(uid)
118}
1196
1207/// Per-repo authorization — the central ACL that was previously only on
1218/// the SSH path (`ssh/handler/command.rs::authorize_repo`) and missing
Modified lib/git/Cargo.toml lib/git/Cargo.toml +0 −1 View lib/git/Cargo.toml
@@ -22,7 +22,6 @@
2222uuid = { workspace = true }
2323time = { workspace = true }
2424globset = { workspace = true }
25base64 = { workspace = true }
2625sha2 = { workspace = true }
2726actix-web = { workspace = true }
2827futures = { workspace = true }
Deleted lib/models/src/users/access_scope.rs lib/models/src/users/access_scope.rs +0 −18
@@ -1,18 +0,0 @@
1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize, utoipa::ToSchema)]
4#[serde(rename_all = "snake_case")]
5pub enum AccessKeyScope {
6 #[default]
7 Read,
8 Write,
9}
10
11impl AccessKeyScope {
12 pub const fn allows(self, want_write: bool) -> bool {
13 match self {
14 Self::Write => true,
15 Self::Read => !want_write,
16 }
17 }
18}
Modified lib/models/src/users/mod.rs lib/models/src/users/mod.rs +0 −1 View lib/models/src/users/mod.rs
@@ -1,6 +1,5 @@
11pub const DATABASE: &str = "user";
22
3pub mod access_scope;
43pub mod user;
54pub mod user_agreement_acceptances;
65pub mod user_blocks;
Modified Cargo.lock Cargo.lock +0 −1 View Cargo.lock
@@ -2726,7 +2726,6 @@
27262726 "actix-web",
27272727 "async-trait",
27282728 "auth",
2729 "base64 0.23.1",
27302729 "bytes",
27312730 "cache",
27322731 "config 1.0.0",
Modified openapi.json openapi.json View openapi.json
Diff too large to display.