postrust_proxy/saas/handlers/
auth.rs1use crate::admin::api::ApiResponse;
4use crate::saas::auth::Auth;
5use crate::saas::handlers::{error_response, require_scope, SaasState};
6use crate::saas::types::CreateApiKeyRequest;
7use axum::{
8 extract::{Path, State},
9 http::StatusCode,
10 response::IntoResponse,
11 Json,
12};
13use serde::Serialize;
14use uuid::Uuid;
15
16pub async fn create_api_key(
23 State(state): State<SaasState>,
24 Auth(auth): Auth,
25 Json(req): Json<CreateApiKeyRequest>,
26) -> impl IntoResponse {
27 if let Err(refusal) = require_scope(&auth, "api_keys:write") {
28 return refusal;
29 }
30 if let Err(e) = crate::saas::auth::validate_scopes(&req.scopes) {
32 return error_response(e).into_response();
33 }
34 if let Some(scope) = req.scopes.iter().find(|scope| !auth.grants(scope)) {
35 return (
36 StatusCode::FORBIDDEN,
37 Json(ApiResponse::<()>::error_with_code(
38 "insufficient_scope",
39 format!("A key cannot be given the {scope} scope by a credential that lacks it"),
40 )),
41 )
42 .into_response();
43 }
44
45 match state
46 .api_key_service
47 .create_api_key(auth.tenant_id, req)
48 .await
49 {
50 Ok(api_key) => (StatusCode::CREATED, Json(ApiResponse::success(api_key))).into_response(),
51 Err(e) => error_response(e).into_response(),
52 }
53}
54
55pub async fn list_api_keys(State(state): State<SaasState>, Auth(auth): Auth) -> impl IntoResponse {
57 if let Err(refusal) = require_scope(&auth, "api_keys:read") {
58 return refusal;
59 }
60
61 match state.api_key_service.list_api_keys(auth.tenant_id).await {
62 Ok(keys) => Json(ApiResponse::success(keys)).into_response(),
63 Err(e) => error_response(e).into_response(),
64 }
65}
66
67pub async fn revoke_api_key(
73 State(state): State<SaasState>,
74 Auth(auth): Auth,
75 Path(id): Path<Uuid>,
76) -> impl IntoResponse {
77 if let Err(refusal) = require_scope(&auth, "api_keys:write") {
78 return refusal;
79 }
80
81 match state.api_key_service.get_api_key(id, auth.tenant_id).await {
82 Ok(Some(target)) => {
83 if let Some(scope) = target.scopes.iter().find(|scope| !auth.grants(scope)) {
84 return (
85 StatusCode::FORBIDDEN,
86 Json(ApiResponse::<()>::error_with_code(
87 "insufficient_scope",
88 format!(
89 "This key holds the {scope} scope, which the caller lacks, \
90 and cannot be revoked by it"
91 ),
92 )),
93 )
94 .into_response();
95 }
96 }
97 Ok(None) => {
98 return (
99 StatusCode::NOT_FOUND,
100 Json(ApiResponse::<()>::error_with_code(
101 "not_found",
102 "API key not found",
103 )),
104 )
105 .into_response()
106 }
107 Err(e) => return error_response(e).into_response(),
108 }
109
110 match state
111 .api_key_service
112 .revoke_api_key(id, auth.tenant_id)
113 .await
114 {
115 Ok(true) => Json(ApiResponse::success(())).into_response(),
116 Ok(false) => (
117 StatusCode::NOT_FOUND,
118 Json(ApiResponse::<()>::error_with_code(
119 "not_found",
120 "API key not found",
121 )),
122 )
123 .into_response(),
124 Err(e) => error_response(e).into_response(),
125 }
126}
127
128#[derive(Serialize)]
130pub struct CurrentTenantResponse {
131 pub tenant_id: Uuid,
133 pub auth_type: String,
135 pub scopes: Vec<String>,
137}
138
139pub async fn get_current_tenant(Auth(auth): Auth) -> impl IntoResponse {
141 if let Err(refusal) = require_scope(&auth, "tenant:read") {
142 return refusal;
143 }
144
145 let auth_type = match &auth.auth_type {
146 crate::saas::auth::AuthType::Jwt { .. } => "jwt",
147 crate::saas::auth::AuthType::ApiKey { .. } => "api_key",
148 };
149
150 Json(ApiResponse::success(CurrentTenantResponse {
151 tenant_id: auth.tenant_id,
152 auth_type: auth_type.to_string(),
153 scopes: auth.scopes,
154 }))
155 .into_response()
156}
157
158pub async fn get_tenant_usage(
160 State(state): State<SaasState>,
161 Auth(auth): Auth,
162) -> impl IntoResponse {
163 if let Err(refusal) = require_scope(&auth, "tenant:read") {
164 return refusal;
165 }
166
167 match state.domain_manager.get_tenant_usage(auth.tenant_id).await {
168 Ok(usage) => Json(ApiResponse::success(usage)).into_response(),
169 Err(e) => error_response(e).into_response(),
170 }
171}