Skip to main content

haste_server/auth_n/oidc/routes/
scope.rs

1use crate::{
2    // auth_n::oidc::extract::client_app::OIDCClientApplication,
3    // extract::path_tenant::{Project, Tenant},
4    auth_n::{
5        oidc::{
6            error::{OIDCError, OIDCErrorCode},
7            extract::client_app::OIDCClientApplication,
8            routes::route_string::oidc_route_string,
9        },
10        session,
11    },
12    extract::path_tenant::{ProjectIdentifier, TenantIdentifier},
13    services::AppState,
14};
15use axum::{
16    Form,
17    extract::{OriginalUri, State},
18    response::{IntoResponse, Response},
19};
20use axum_extra::{extract::Cached, routing::TypedPath};
21use haste_fhir_search::SearchEngine;
22use haste_fhir_terminology::FHIRTerminology;
23use haste_jwt::scopes::Scopes;
24use haste_repository::{
25    Repository,
26    admin::ProjectAuthAdmin,
27    types::scope::{ClientId, CreateScope, UserId},
28};
29use serde::Deserialize;
30use std::sync::Arc;
31use tower_sessions::Session;
32
33#[derive(TypedPath)]
34#[typed_path("/scope")]
35pub struct ScopePost;
36
37#[derive(Deserialize, Debug)]
38pub struct ScopeForm {
39    pub client_id: String,
40    pub response_type: String,
41    pub state: String,
42    pub code_challenge: String,
43    pub code_challenge_method: String,
44    pub scope: haste_jwt::scopes::Scopes,
45    pub redirect_uri: String,
46    pub accept: Option<String>,
47}
48
49pub fn verify_requested_scope_is_subset(
50    requested: &Scopes,
51    allowed: &Scopes,
52) -> Result<(), OIDCError> {
53    for scope in requested.0.iter() {
54        if !allowed.0.contains(scope) {
55            return Err(OIDCError::new(
56                OIDCErrorCode::InvalidScope,
57                Some("Requested scope '{}' is not allowed. Check client configuration for what scopes are allowed.".to_string()),
58                 None
59                )
60            );
61        }
62    }
63    Ok(())
64}
65
66pub async fn scope_post<
67    Repo: Repository + Send + Sync,
68    Search: SearchEngine + Send + Sync,
69    Terminology: FHIRTerminology + Send + Sync,
70>(
71    _: ScopePost,
72    _uri: OriginalUri,
73    State(app_state): State<Arc<AppState<Repo, Search, Terminology>>>,
74    Cached(current_session): Cached<Session>,
75    OIDCClientApplication(client_app): OIDCClientApplication,
76    Cached(TenantIdentifier { tenant }): Cached<TenantIdentifier>,
77    Cached(ProjectIdentifier { project }): Cached<ProjectIdentifier>,
78    Form(scope_data): Form<ScopeForm>,
79) -> Result<Response, OIDCError> {
80    let user = session::user::get_user(&current_session)
81        .await
82        .map_err(|_| {
83            OIDCError::new(
84                OIDCErrorCode::ServerError,
85                Some("Failed to retrieve user from session.".to_string()),
86                Some(scope_data.redirect_uri.clone()),
87            )
88        })?
89        .unwrap();
90
91    if let Some("on") = scope_data.accept.as_ref().map(String::as_str) {
92        verify_requested_scope_is_subset(
93            &scope_data.scope,
94            &Scopes::from(
95                client_app
96                    .scope
97                    .as_ref()
98                    .and_then(|s| s.value.clone())
99                    .unwrap_or_default(),
100            ),
101        )?;
102
103        ProjectAuthAdmin::create(
104            &*app_state.repo,
105            &tenant,
106            &project,
107            CreateScope {
108                client: ClientId::new(scope_data.client_id.clone()),
109                user_: UserId::new(user.id),
110                scope: scope_data.scope.clone(),
111            },
112        )
113        .await
114        .map_err(|_| {
115            OIDCError::new(
116                OIDCErrorCode::ServerError,
117                Some("Failed to create scope authorization.".to_string()),
118                Some(scope_data.redirect_uri.clone()),
119            )
120        })?;
121
122        let authorization_route = oidc_route_string(&tenant, &project, "auth/authorize")
123            .to_str()
124            .expect("Could not create authorize route.")
125            .to_string()
126            + "?client_id="
127            + scope_data.client_id.as_str()
128            + "&response_type="
129            + scope_data.response_type.as_str()
130            + "&state="
131            + scope_data.state.as_str()
132            + "&code_challenge="
133            + scope_data.code_challenge.as_str()
134            + "&code_challenge_method="
135            + scope_data.code_challenge_method.as_str()
136            + "&scope="
137            + String::from(scope_data.scope).as_str()
138            + "&redirect_uri="
139            + scope_data.redirect_uri.as_str();
140        let redirect = axum::response::Redirect::to(&authorization_route);
141        Ok(redirect.into_response())
142    } else {
143        Err(OIDCError::new(
144            OIDCErrorCode::AccessDenied,
145            Some("User did not accept the requested scopes.".to_string()),
146            Some(scope_data.redirect_uri),
147        ))
148    }
149}