1use std::rc::Rc;
8
9use lenso_auth_sdk::{
10 ActorAssertion, AuthOutcome, CredentialEvidence, authenticate_request, decode_auth_response,
11};
12use lenso_capability_auth::{AuthClient, AuthInvocationError, AuthenticateError};
13use lenso_capability_http_endpoint::{
14 EndpointHandleInvocationError, ExtractorFuture, HandleRequest, HandleResponse,
15 response::{self, HeaderValue, StatusCode, header},
16};
17use lenso_kernel::{InvocationContext, RuntimeFailure};
18
19pub trait AuthClientSource {
21 fn auth_client(&self) -> Result<Rc<AuthClient>, EndpointHandleInvocationError>;
23}
24
25pub trait AuthenticatedHttpActor: Sized {
34 const KIND: &'static str;
36
37 fn from_assertion(assertion: &ActorAssertion) -> Self;
39}
40
41pub fn extract_authenticated_actor<'a, P, A>(
49 provider: &'a P,
50 context: &'a mut InvocationContext,
51 request: &'a HandleRequest,
52) -> ExtractorFuture<'a, A>
53where
54 P: AuthClientSource + ?Sized,
55 A: AuthenticatedHttpActor + 'a,
56{
57 Box::pin(async move {
58 let auth = provider.auth_client()?;
59 let evidence = request.credential.as_ref().map(|credential| {
60 CredentialEvidence::new(credential.scheme.clone(), credential.value.clone())
61 });
62 let response = match auth
63 .authenticate_with_context(context.clone(), authenticate_request(evidence))
64 .await
65 {
66 Ok(response) => response,
67 Err(AuthInvocationError::Domain(error)) => {
68 return Err(authentication_rejection(&error)?.into());
69 }
70 Err(AuthInvocationError::Runtime(error)) => {
71 return Err(EndpointHandleInvocationError::Runtime(error).into());
72 }
73 };
74 let assertion = match decode_auth_response(response).map_err(internal)? {
75 AuthOutcome::Absent => {
76 return Err(unauthorized(
77 "authentication_required",
78 "Authentication credentials are required.",
79 )?
80 .into());
81 }
82 AuthOutcome::Authenticated(assertion) => assertion,
83 };
84 if assertion.actor_kind() != A::KIND {
85 return Err(response::problem(
86 StatusCode::FORBIDDEN,
87 "unexpected_actor_kind",
88 "The authenticated actor cannot access this endpoint.",
89 )
90 .into());
91 }
92 *context = assertion.attach(context.clone()).map_err(internal)?;
93 Ok(A::from_assertion(&assertion))
94 })
95}
96
97fn authentication_rejection(
98 error: &AuthenticateError,
99) -> Result<HandleResponse, EndpointHandleInvocationError> {
100 let (code, detail) = match error {
101 AuthenticateError::Expired => (
102 "expired_credential",
103 "The supplied authentication credential has expired.",
104 ),
105 AuthenticateError::Invalid => (
106 "invalid_credential",
107 "The supplied authentication credential is invalid.",
108 ),
109 AuthenticateError::Revoked => (
110 "revoked_credential",
111 "The supplied authentication credential has been revoked.",
112 ),
113 AuthenticateError::Unsupported => (
114 "unsupported_credential",
115 "The supplied authentication credential is not supported.",
116 ),
117 AuthenticateError::Unknown(_) => (
118 "authentication_failed",
119 "The supplied authentication credential was not accepted.",
120 ),
121 };
122 unauthorized(code, detail)
123}
124
125fn unauthorized(
126 code: &'static str,
127 detail: &'static str,
128) -> Result<HandleResponse, EndpointHandleInvocationError> {
129 Ok(
130 response::problem(StatusCode::UNAUTHORIZED, code, detail).with_header(
131 &header::WWW_AUTHENTICATE,
132 &HeaderValue::from_static("Bearer"),
133 )?,
134 )
135}
136
137fn internal(error: impl std::fmt::Debug) -> EndpointHandleInvocationError {
138 EndpointHandleInvocationError::Runtime(RuntimeFailure::Internal {
139 detail: format!("{error:?}"),
140 })
141}