ts_webapi/
cors.rs

1//! Convenience setup for CORS
2
3use core::net::{Ipv4Addr, Ipv6Addr};
4
5use http::{
6    HeaderName, Method, Uri,
7    header::{ACCEPT, AUTHORIZATION, CONTENT_ENCODING, CONTENT_TYPE},
8};
9use tower_http::cors::{AllowOrigin, CorsLayer};
10
11/// CORS layer where the common HTTP methods, headers, and localhost are all allowed by default.
12pub fn cors_layer(
13    additional_allowed_origins: Vec<Uri>,
14    additional_allowed_headers: &[HeaderName],
15    additional_exposed_headers: &[HeaderName],
16) -> CorsLayer {
17    let mut allowed_headers = vec![AUTHORIZATION, ACCEPT, CONTENT_TYPE];
18    allowed_headers.extend_from_slice(additional_allowed_headers);
19
20    let mut exposed_headers = vec![AUTHORIZATION, CONTENT_ENCODING, CONTENT_TYPE];
21    exposed_headers.extend_from_slice(additional_exposed_headers);
22
23    let allowed_methods = [
24        Method::OPTIONS,
25        Method::HEAD,
26        Method::GET,
27        Method::PUT,
28        Method::POST,
29        Method::DELETE,
30    ];
31
32    let allowed_origins = AllowOrigin::predicate(move |header, _| {
33        let Ok(origin) = header.to_str() else {
34            return false;
35        };
36        let Ok(origin) = Uri::try_from(origin) else {
37            return false;
38        };
39        let Some(host) = origin.host() else {
40            return false;
41        };
42
43        // Allow localhost regardless of port or scheme.
44        if host == "localhost"
45            || host.parse::<Ipv4Addr>() == Ok(Ipv4Addr::LOCALHOST)
46            || host.parse::<Ipv6Addr>() == Ok(Ipv6Addr::LOCALHOST)
47        {
48            return true;
49        }
50
51        // Allow origin if it matches the scheme, host, and port of an allowed origin.
52        additional_allowed_origins.iter().any(|allowed_origin| {
53            allowed_origin.scheme().eq(&origin.scheme())
54                && allowed_origin.host().eq(&origin.host())
55                && allowed_origin.port().eq(&origin.port())
56        })
57    });
58
59    CorsLayer::new()
60        .allow_origin(allowed_origins)
61        .allow_credentials(true)
62        .allow_headers(allowed_headers)
63        .allow_methods(allowed_methods)
64        .expose_headers(exposed_headers)
65}