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