Skip to content

Commit

Permalink
[Workers] Adds New Rust Examples (#17302)
Browse files Browse the repository at this point in the history
* feat: Adds rust example for websocket

* [Workers] Adds rust example logging-headers

* [Workers] Adds rust example basic-auth

* [Workers] Adds rust example security-headers

* [Workers] Adds rust example cors-header-proxy
  • Loading branch information
megaconfidence authored Oct 14, 2024
1 parent 055ef5e commit cc522bd
Show file tree
Hide file tree
Showing 5 changed files with 395 additions and 11 deletions.
74 changes: 73 additions & 1 deletion src/content/docs/workers/examples/basic-auth.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ tags:
languages:
- JavaScript
- TypeScript
- Rust
preview:
- true
pcx_content_type: example
Expand Down Expand Up @@ -258,4 +259,75 @@ export default {
} satisfies ExportedHandler<Env>;
```
</TabItem> </Tabs>
</TabItem> <TabItem label="Rust" icon="seti:rust">
```rs
use base64::prelude::*;
use worker::*;

#[event(fetch)]
async fn fetch(req: Request, env: Env, _ctx: Context) -> Result<Response> {
let basic_user = "admin";
// You will need an admin password. This should be
// attached to your Worker as an encrypted secret.
// Refer to https://developers.cloudflare.com/workers/configuration/secrets/
let basic_pass = match env.secret("PASSWORD") {
Ok(s) => s.to_string(),
Err(_) => "password".to_string(),
};
let url = req.url()?;

match url.path() {
"/" => Response::ok("Anyone can access the homepage."),
// Invalidate the "Authorization" header by returning a HTTP 401.
// We do not send a "WWW-Authenticate" header, as this would trigger
// a popup in the browser, immediately asking for credentials again.
"/logout" => Response::error("Logged out.", 401),
"/admin" => {
// The "Authorization" header is sent when authenticated.
let authorization = req.headers().get("Authorization")?;
if authorization == None {
let mut headers = Headers::new();
// Prompts the user for credentials.
headers.set(
"WWW-Authenticate",
"Basic realm='my scope', charset='UTF-8'",
)?;
return Ok(Response::error("You need to login.", 401)?.with_headers(headers));
}
let authorization = authorization.unwrap();
let auth: Vec<&str> = authorization.split(" ").collect();
let scheme = auth[0];
let encoded = auth[1];

// The Authorization header must start with Basic, followed by a space.
if encoded == "" || scheme != "Basic" {
return Response::error("Malformed authorization header.", 400);
}

let buff = BASE64_STANDARD.decode(encoded).unwrap();
let credentials = String::from_utf8_lossy(&buff);
// The username & password are split by the first colon.
//=> example: "username:password"
let credentials: Vec<&str> = credentials.split(':').collect();
let user = credentials[0];
let pass = credentials[1];

if user != basic_user || pass != basic_pass {
let mut headers = Headers::new();
// Prompts the user for credentials.
headers.set(
"WWW-Authenticate",
"Basic realm='my scope', charset='UTF-8'",
)?;
return Ok(Response::error("You need to login.", 401)?.with_headers(headers));
}

let mut headers = Headers::new();
headers.set("Cache-Control", "no-store")?;
Ok(Response::ok("🎉 You have private access!")?.with_headers(headers))
}
_ => Response::error("Not Found.", 404),
}
}
```
</TabItem> </Tabs>
127 changes: 126 additions & 1 deletion src/content/docs/workers/examples/cors-header-proxy.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ languages:
- JavaScript
- TypeScript
- Python
- Rust
pcx_content_type: example
title: CORS header proxy
sidebar:
Expand Down Expand Up @@ -436,4 +437,128 @@ async def on_fetch(request):
return raw_html_response(demo_page)
```

</TabItem> </Tabs>
</TabItem> <TabItem label="Rust" icon="seti:rust">
```rs
use std::{borrow::Cow, collections::HashMap};
use worker::*;

fn raw_html_response(html: &str) -> Result<Response> {
Response::from_html(html)
}
async fn handle_request(req: Request, api_url: &str) -> Result<Response> {
let url = req.url().unwrap();
let mut api_url2 = url
.query_pairs()
.find(|x| x.0 == Cow::Borrowed("apiurl"))
.unwrap()
.1
.to_string();
if api_url2 == String::from("") {
api_url2 = api_url.to_string();
}
let mut request = req.clone_mut()?;
*request.path_mut()? = api_url2.clone();
if let url::Origin::Tuple(origin, _, _) = Url::parse(&api_url2)?.origin() {
(*request.headers_mut()?).set("Origin", &origin)?;
}
let mut response = Fetch::Request(request).send().await?.cloned()?;
let headers = response.headers_mut();
if let url::Origin::Tuple(origin, _, _) = url.origin() {
headers.set("Access-Control-Allow-Origin", &origin)?;
headers.set("Vary", "Origin")?;
}

Ok(response)
}

fn handle_options(req: Request, cors_headers: &HashMap<&str, &str>) -> Result<Response> {
let headers: Vec<_> = req.headers().keys().collect();
if [
"access-control-request-method",
"access-control-request-headers",
"origin",
]
.iter()
.all(|i| headers.contains(&i.to_string()))
{
let mut headers = Headers::new();
for (k, v) in cors_headers.iter() {
headers.set(k, v)?;
}
return Ok(Response::empty()?.with_headers(headers));
}
Response::empty()
}
#[event(fetch)]
async fn fetch(req: Request, _env: Env, _ctx: Context) -> Result<Response> {
let cors_headers = HashMap::from([
("Access-Control-Allow-Origin", "*"),
("Access-Control-Allow-Methods", "GET,HEAD,POST,OPTIONS"),
("Access-Control-Max-Age", "86400"),
]);
let api_url = "https://examples.cloudflareworkers.com/demos/demoapi";
let proxy_endpoint = "/corsproxy/";
let demo_page = format!(
r#"
<!DOCTYPE html>
<html>
<body>
<h1>API GET without CORS Proxy</h1>
<a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch#Checking_that_the_fetch_was_successful">Shows TypeError: Failed to fetch since CORS is misconfigured</a>
<p id="noproxy-status"/>
<code id="noproxy">Waiting</code>
<h1>API GET with CORS Proxy</h1>
<p id="proxy-status"/>
<code id="proxy">Waiting</code>
<h1>API POST with CORS Proxy + Preflight</h1>
<p id="proxypreflight-status"/>
<code id="proxypreflight">Waiting</code>
<script>
let reqs = {{}};
reqs.noproxy = () => {{
return fetch("{api_url}").then(r => r.json())
}}
reqs.proxy = async () => {{
let href = "{proxy_endpoint}?apiurl={api_url}"
return fetch(window.location.origin + href).then(r => r.json())
}}
reqs.proxypreflight = async () => {{
let href = "{proxy_endpoint}?apiurl={api_url}"
let response = await fetch(window.location.origin + href, {{
method: "POST",
headers: {{
"Content-Type": "application/json"
}},
body: JSON.stringify({{
msg: "Hello world!"
}})
}})
return response.json()
}}
(async () => {{
for (const [reqName, req] of Object.entries(reqs)) {{
try {{
let data = await req()
document.getElementById(reqName).innerHTML = JSON.stringify(data)
}} catch (e) {{
document.getElementById(reqName).innerHTML = e
}}
}}
}})()
</script>
</body>
</html>
"#
);

if req.url()?.path().starts_with(proxy_endpoint) {
match req.method() {
Method::Options => return handle_options(req, &cors_headers),
Method::Get | Method::Head | Method::Post => return handle_request(req, api_url).await,
_ => return Response::error("Method Not Allowed", 405),
}
}
raw_html_response(&demo_page)
}
```
</TabItem> </Tabs>
13 changes: 12 additions & 1 deletion src/content/docs/workers/examples/logging-headers.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ tags:
- Headers
languages:
- JavaScript
- Rust
- TypeScript
- Python
preview:
Expand Down Expand Up @@ -51,6 +52,16 @@ async def on_fetch(request):
return Response.new('Hello world')
```

</TabItem> <TabItem label="Rust" icon="seti:rust">
```rs
use worker::*;

#[event(fetch)]
async fn fetch(req: HttpRequest, _env: Env, _ctx: Context) -> Result<Response> {
console_log!("{:?}", req.headers());
Response::ok("hello world")
}
```
</TabItem> </Tabs>

---
Expand Down Expand Up @@ -142,4 +153,4 @@ Request headers: {
"cf-ipcountry": "US",
// ...
}"
```
```
74 changes: 73 additions & 1 deletion src/content/docs/workers/examples/security-headers.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ languages:
- JavaScript
- TypeScript
- Python
- Rust
pcx_content_type: example
title: Set security headers
sidebar:
Expand Down Expand Up @@ -250,4 +251,75 @@ async def on_fetch(request):
return Response.new(res.body, status=res.status, statusText=res.statusText, headers=new_headers)
```

</TabItem> </Tabs>
</TabItem> <TabItem label="Rust" icon="seti:rust">
```rs
use std::collections::HashMap;
use worker::*;

#[event(fetch)]
async fn fetch(req: Request, _env: Env, _ctx: Context) -> Result<Response> {
let default_security_headers = HashMap::from([
//Secure your application with Content-Security-Policy headers.
//Enabling these headers will permit content from a trusted domain and all its subdomains.
//@see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
(
"Content-Security-Policy",
"default-src 'self' example.com *.example.com",
),
//You can also set Strict-Transport-Security headers.
//These are not automatically set because your website might get added to Chrome's HSTS preload list.
//Here's the code if you want to apply it:
(
"Strict-Transport-Security",
"max-age=63072000; includeSubDomains; preload",
),
//Permissions-Policy header provides the ability to allow or deny the use of browser features, such as opting out of FLoC - which you can use below:
("Permissions-Policy", "interest-cohort=()"),
//X-XSS-Protection header prevents a page from loading if an XSS attack is detected.
//@see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection
("X-XSS-Protection", "0"),
//X-Frame-Options header prevents click-jacking attacks.
//@see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options
("X-Frame-Options", "DENY"),
//X-Content-Type-Options header prevents MIME-sniffing.
//@see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options
("X-Content-Type-Options", "nosniff"),
("Referrer-Policy", "strict-origin-when-cross-origin"),
(
"Cross-Origin-Embedder-Policy",
"require-corp; report-to='default';",
),
(
"Cross-Origin-Opener-Policy",
"same-site; report-to='default';",
),
("Cross-Origin-Resource-Policy", "same-site"),
]);
let blocked_headers = ["Public-Key-Pins", "X-Powered-By", "X-AspNet-Version"];
let tls = req.cf().unwrap().tls_version();
let res = Fetch::Request(req).send().await?;
let mut new_headers = res.headers().clone();

// This sets the headers for HTML responses
if Some(String::from("text/html")) == new_headers.get("Content-Type")? {
return Ok(Response::from_body(res.body().clone())?
.with_headers(new_headers)
.with_status(res.status_code()));
}
for (k, v) in default_security_headers {
new_headers.set(k, v)?;
}

for k in blocked_headers {
new_headers.delete(k)?;
}

if !vec!["TLSv1.2", "TLSv1.3"].contains(&tls.as_str()) {
return Response::error("You need to use TLS version 1.2 or higher.", 400);
}
Ok(Response::from_body(res.body().clone())?
.with_headers(new_headers)
.with_status(res.status_code()))
}
```
</TabItem> </Tabs>
Loading

0 comments on commit cc522bd

Please sign in to comment.