(&self, token: &str)
| 164 | |
| 165 | #[allow(clippy::result_large_err)] |
| 166 | fn validate_bearer(&self, token: &str) -> Result<Option<Principal>, Status> { |
| 167 | let header = decode_header(token).map_err(|e| { |
| 168 | debug!(error = %e, "sandbox JWT header decode failed"); |
| 169 | Status::unauthenticated("invalid token") |
| 170 | })?; |
| 171 | |
| 172 | // Fall through to other authenticators when the kid does not match — |
| 173 | // OIDC issuers may share the Bearer slot. |
| 174 | if header.kid.as_deref() != Some(self.kid.as_str()) { |
| 175 | return Ok(None); |
| 176 | } |
| 177 | if !matches!(header.alg, Algorithm::EdDSA) { |
| 178 | return Ok(None); |
| 179 | } |
| 180 | |
| 181 | let mut validation = Validation::new(Algorithm::EdDSA); |
| 182 | validation.algorithms = vec![Algorithm::EdDSA]; |
| 183 | validation.set_issuer(&[&self.issuer]); |
| 184 | validation.set_audience(&[&self.audience]); |
| 185 | validation.set_required_spec_claims(&["iss", "aud", "exp", "sub"]); |
| 186 | validation.validate_exp = false; |
| 187 | |
| 188 | let data = |
| 189 | decode::<SandboxJwtClaims>(token, &self.decoding_key, &validation).map_err(|e| { |
| 190 | debug!(error = %e, "sandbox JWT validation failed"); |
| 191 | Status::unauthenticated(format!("invalid token: {e}")) |
| 192 | })?; |
| 193 | |
| 194 | let claims = data.claims; |
| 195 | validate_exp(claims.exp)?; |
| 196 | Ok(Some(Principal::Sandbox(SandboxPrincipal { |
| 197 | sandbox_id: claims.sandbox_id, |
| 198 | source: SandboxIdentitySource::BootstrapJwt { issuer: claims.iss }, |
| 199 | trust_domain: Some("openshell".to_string()), |
| 200 | }))) |
| 201 | } |
| 202 | } |
| 203 | |
| 204 | #[async_trait] |
no test coverage detected