Skip to content

Commit c3f0d9e

Browse files
megaconfidenceelithrar
authored andcommitted
[Workers] Adds New Rust Examples (#17302)
* 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
1 parent 97d445c commit c3f0d9e

File tree

5 files changed

+395
-11
lines changed

5 files changed

+395
-11
lines changed

src/content/docs/workers/examples/basic-auth.mdx

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ tags:
77
languages:
88
- JavaScript
99
- TypeScript
10+
- Rust
1011
preview:
1112
- true
1213
pcx_content_type: example
@@ -258,4 +259,75 @@ export default {
258259
} satisfies ExportedHandler<Env>;
259260
```
260261
261-
</TabItem> </Tabs>
262+
</TabItem> <TabItem label="Rust" icon="seti:rust">
263+
```rs
264+
use base64::prelude::*;
265+
use worker::*;
266+
267+
#[event(fetch)]
268+
async fn fetch(req: Request, env: Env, _ctx: Context) -> Result<Response> {
269+
let basic_user = "admin";
270+
// You will need an admin password. This should be
271+
// attached to your Worker as an encrypted secret.
272+
// Refer to https://developers.cloudflare.com/workers/configuration/secrets/
273+
let basic_pass = match env.secret("PASSWORD") {
274+
Ok(s) => s.to_string(),
275+
Err(_) => "password".to_string(),
276+
};
277+
let url = req.url()?;
278+
279+
match url.path() {
280+
"/" => Response::ok("Anyone can access the homepage."),
281+
// Invalidate the "Authorization" header by returning a HTTP 401.
282+
// We do not send a "WWW-Authenticate" header, as this would trigger
283+
// a popup in the browser, immediately asking for credentials again.
284+
"/logout" => Response::error("Logged out.", 401),
285+
"/admin" => {
286+
// The "Authorization" header is sent when authenticated.
287+
let authorization = req.headers().get("Authorization")?;
288+
if authorization == None {
289+
let mut headers = Headers::new();
290+
// Prompts the user for credentials.
291+
headers.set(
292+
"WWW-Authenticate",
293+
"Basic realm='my scope', charset='UTF-8'",
294+
)?;
295+
return Ok(Response::error("You need to login.", 401)?.with_headers(headers));
296+
}
297+
let authorization = authorization.unwrap();
298+
let auth: Vec<&str> = authorization.split(" ").collect();
299+
let scheme = auth[0];
300+
let encoded = auth[1];
301+
302+
// The Authorization header must start with Basic, followed by a space.
303+
if encoded == "" || scheme != "Basic" {
304+
return Response::error("Malformed authorization header.", 400);
305+
}
306+
307+
let buff = BASE64_STANDARD.decode(encoded).unwrap();
308+
let credentials = String::from_utf8_lossy(&buff);
309+
// The username & password are split by the first colon.
310+
//=> example: "username:password"
311+
let credentials: Vec<&str> = credentials.split(':').collect();
312+
let user = credentials[0];
313+
let pass = credentials[1];
314+
315+
if user != basic_user || pass != basic_pass {
316+
let mut headers = Headers::new();
317+
// Prompts the user for credentials.
318+
headers.set(
319+
"WWW-Authenticate",
320+
"Basic realm='my scope', charset='UTF-8'",
321+
)?;
322+
return Ok(Response::error("You need to login.", 401)?.with_headers(headers));
323+
}
324+
325+
let mut headers = Headers::new();
326+
headers.set("Cache-Control", "no-store")?;
327+
Ok(Response::ok("🎉 You have private access!")?.with_headers(headers))
328+
}
329+
_ => Response::error("Not Found.", 404),
330+
}
331+
}
332+
```
333+
</TabItem> </Tabs>

src/content/docs/workers/examples/cors-header-proxy.mdx

Lines changed: 126 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ languages:
88
- JavaScript
99
- TypeScript
1010
- Python
11+
- Rust
1112
pcx_content_type: example
1213
title: CORS header proxy
1314
sidebar:
@@ -436,4 +437,128 @@ async def on_fetch(request):
436437
return raw_html_response(demo_page)
437438
```
438439

439-
</TabItem> </Tabs>
440+
</TabItem> <TabItem label="Rust" icon="seti:rust">
441+
```rs
442+
use std::{borrow::Cow, collections::HashMap};
443+
use worker::*;
444+
445+
fn raw_html_response(html: &str) -> Result<Response> {
446+
Response::from_html(html)
447+
}
448+
async fn handle_request(req: Request, api_url: &str) -> Result<Response> {
449+
let url = req.url().unwrap();
450+
let mut api_url2 = url
451+
.query_pairs()
452+
.find(|x| x.0 == Cow::Borrowed("apiurl"))
453+
.unwrap()
454+
.1
455+
.to_string();
456+
if api_url2 == String::from("") {
457+
api_url2 = api_url.to_string();
458+
}
459+
let mut request = req.clone_mut()?;
460+
*request.path_mut()? = api_url2.clone();
461+
if let url::Origin::Tuple(origin, _, _) = Url::parse(&api_url2)?.origin() {
462+
(*request.headers_mut()?).set("Origin", &origin)?;
463+
}
464+
let mut response = Fetch::Request(request).send().await?.cloned()?;
465+
let headers = response.headers_mut();
466+
if let url::Origin::Tuple(origin, _, _) = url.origin() {
467+
headers.set("Access-Control-Allow-Origin", &origin)?;
468+
headers.set("Vary", "Origin")?;
469+
}
470+
471+
Ok(response)
472+
}
473+
474+
fn handle_options(req: Request, cors_headers: &HashMap<&str, &str>) -> Result<Response> {
475+
let headers: Vec<_> = req.headers().keys().collect();
476+
if [
477+
"access-control-request-method",
478+
"access-control-request-headers",
479+
"origin",
480+
]
481+
.iter()
482+
.all(|i| headers.contains(&i.to_string()))
483+
{
484+
let mut headers = Headers::new();
485+
for (k, v) in cors_headers.iter() {
486+
headers.set(k, v)?;
487+
}
488+
return Ok(Response::empty()?.with_headers(headers));
489+
}
490+
Response::empty()
491+
}
492+
#[event(fetch)]
493+
async fn fetch(req: Request, _env: Env, _ctx: Context) -> Result<Response> {
494+
let cors_headers = HashMap::from([
495+
("Access-Control-Allow-Origin", "*"),
496+
("Access-Control-Allow-Methods", "GET,HEAD,POST,OPTIONS"),
497+
("Access-Control-Max-Age", "86400"),
498+
]);
499+
let api_url = "https://examples.cloudflareworkers.com/demos/demoapi";
500+
let proxy_endpoint = "/corsproxy/";
501+
let demo_page = format!(
502+
r#"
503+
<!DOCTYPE html>
504+
<html>
505+
<body>
506+
<h1>API GET without CORS Proxy</h1>
507+
<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>
508+
<p id="noproxy-status"/>
509+
<code id="noproxy">Waiting</code>
510+
<h1>API GET with CORS Proxy</h1>
511+
<p id="proxy-status"/>
512+
<code id="proxy">Waiting</code>
513+
<h1>API POST with CORS Proxy + Preflight</h1>
514+
<p id="proxypreflight-status"/>
515+
<code id="proxypreflight">Waiting</code>
516+
<script>
517+
let reqs = {{}};
518+
reqs.noproxy = () => {{
519+
return fetch("{api_url}").then(r => r.json())
520+
}}
521+
reqs.proxy = async () => {{
522+
let href = "{proxy_endpoint}?apiurl={api_url}"
523+
return fetch(window.location.origin + href).then(r => r.json())
524+
}}
525+
reqs.proxypreflight = async () => {{
526+
let href = "{proxy_endpoint}?apiurl={api_url}"
527+
let response = await fetch(window.location.origin + href, {{
528+
method: "POST",
529+
headers: {{
530+
"Content-Type": "application/json"
531+
}},
532+
body: JSON.stringify({{
533+
msg: "Hello world!"
534+
}})
535+
}})
536+
return response.json()
537+
}}
538+
(async () => {{
539+
for (const [reqName, req] of Object.entries(reqs)) {{
540+
try {{
541+
let data = await req()
542+
document.getElementById(reqName).innerHTML = JSON.stringify(data)
543+
}} catch (e) {{
544+
document.getElementById(reqName).innerHTML = e
545+
}}
546+
}}
547+
}})()
548+
</script>
549+
</body>
550+
</html>
551+
"#
552+
);
553+
554+
if req.url()?.path().starts_with(proxy_endpoint) {
555+
match req.method() {
556+
Method::Options => return handle_options(req, &cors_headers),
557+
Method::Get | Method::Head | Method::Post => return handle_request(req, api_url).await,
558+
_ => return Response::error("Method Not Allowed", 405),
559+
}
560+
}
561+
raw_html_response(&demo_page)
562+
}
563+
```
564+
</TabItem> </Tabs>

src/content/docs/workers/examples/logging-headers.mdx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ tags:
66
- Headers
77
languages:
88
- JavaScript
9+
- Rust
910
- TypeScript
1011
- Python
1112
preview:
@@ -51,6 +52,16 @@ async def on_fetch(request):
5152
return Response.new('Hello world')
5253
```
5354

55+
</TabItem> <TabItem label="Rust" icon="seti:rust">
56+
```rs
57+
use worker::*;
58+
59+
#[event(fetch)]
60+
async fn fetch(req: HttpRequest, _env: Env, _ctx: Context) -> Result<Response> {
61+
console_log!("{:?}", req.headers());
62+
Response::ok("hello world")
63+
}
64+
```
5465
</TabItem> </Tabs>
5566

5667
---
@@ -142,4 +153,4 @@ Request headers: {
142153
"cf-ipcountry": "US",
143154
// ...
144155
}"
145-
```
156+
```

src/content/docs/workers/examples/security-headers.mdx

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ languages:
1010
- JavaScript
1111
- TypeScript
1212
- Python
13+
- Rust
1314
pcx_content_type: example
1415
title: Set security headers
1516
sidebar:
@@ -250,4 +251,75 @@ async def on_fetch(request):
250251
return Response.new(res.body, status=res.status, statusText=res.statusText, headers=new_headers)
251252
```
252253

253-
</TabItem> </Tabs>
254+
</TabItem> <TabItem label="Rust" icon="seti:rust">
255+
```rs
256+
use std::collections::HashMap;
257+
use worker::*;
258+
259+
#[event(fetch)]
260+
async fn fetch(req: Request, _env: Env, _ctx: Context) -> Result<Response> {
261+
let default_security_headers = HashMap::from([
262+
//Secure your application with Content-Security-Policy headers.
263+
//Enabling these headers will permit content from a trusted domain and all its subdomains.
264+
//@see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
265+
(
266+
"Content-Security-Policy",
267+
"default-src 'self' example.com *.example.com",
268+
),
269+
//You can also set Strict-Transport-Security headers.
270+
//These are not automatically set because your website might get added to Chrome's HSTS preload list.
271+
//Here's the code if you want to apply it:
272+
(
273+
"Strict-Transport-Security",
274+
"max-age=63072000; includeSubDomains; preload",
275+
),
276+
//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:
277+
("Permissions-Policy", "interest-cohort=()"),
278+
//X-XSS-Protection header prevents a page from loading if an XSS attack is detected.
279+
//@see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection
280+
("X-XSS-Protection", "0"),
281+
//X-Frame-Options header prevents click-jacking attacks.
282+
//@see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options
283+
("X-Frame-Options", "DENY"),
284+
//X-Content-Type-Options header prevents MIME-sniffing.
285+
//@see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options
286+
("X-Content-Type-Options", "nosniff"),
287+
("Referrer-Policy", "strict-origin-when-cross-origin"),
288+
(
289+
"Cross-Origin-Embedder-Policy",
290+
"require-corp; report-to='default';",
291+
),
292+
(
293+
"Cross-Origin-Opener-Policy",
294+
"same-site; report-to='default';",
295+
),
296+
("Cross-Origin-Resource-Policy", "same-site"),
297+
]);
298+
let blocked_headers = ["Public-Key-Pins", "X-Powered-By", "X-AspNet-Version"];
299+
let tls = req.cf().unwrap().tls_version();
300+
let res = Fetch::Request(req).send().await?;
301+
let mut new_headers = res.headers().clone();
302+
303+
// This sets the headers for HTML responses
304+
if Some(String::from("text/html")) == new_headers.get("Content-Type")? {
305+
return Ok(Response::from_body(res.body().clone())?
306+
.with_headers(new_headers)
307+
.with_status(res.status_code()));
308+
}
309+
for (k, v) in default_security_headers {
310+
new_headers.set(k, v)?;
311+
}
312+
313+
for k in blocked_headers {
314+
new_headers.delete(k)?;
315+
}
316+
317+
if !vec!["TLSv1.2", "TLSv1.3"].contains(&tls.as_str()) {
318+
return Response::error("You need to use TLS version 1.2 or higher.", 400);
319+
}
320+
Ok(Response::from_body(res.body().clone())?
321+
.with_headers(new_headers)
322+
.with_status(res.status_code()))
323+
}
324+
```
325+
</TabItem> </Tabs>

0 commit comments

Comments
 (0)