Add basic demo SPA to display claims

This commit is contained in:
Simon Bihel 2022-02-11 13:54:20 +00:00
parent 15763cd0bb
commit 0f110d70b4
No known key found for this signature in database
GPG Key ID: B7013150BEAA28FD
11 changed files with 258 additions and 2 deletions

View File

@ -1,2 +1,3 @@
.github
example
target

6
.gitignore vendored
View File

@ -1,3 +1,5 @@
/target
**/target
/static/build
wrangler.toml
**/wrangler.toml
**/node_module
**/dist

3
example/demo/.env Normal file
View File

@ -0,0 +1,3 @@
CLIENT_ID="8f169184-8815-457c-a32f-85b39fbcfca7"
CLIENT_SECRET="430d8ade-d2d7-48c1-9c68-b97fcd73967d"
REDIRECT_URI="https://demo-oidc.login.xyz/callback"

17
example/demo/Cargo.toml Normal file
View File

@ -0,0 +1,17 @@
[package]
name = "demo"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
yew = "0.19.3"
yew-router = "0.16.0"
url = "2.2.2"
lazy_static = "1.4.0"
serde = "1.0.136"
openidconnect = {git = "https://github.com/sbihel/openidconnect-rs", branch = "main", default-features = false, features = ["reqwest", "rustls-tls", "rustcrypto"]}
wasm-bindgen-futures = "0.4.29"
serde_json = "1.0.78"
chrono = { version = "0.4.19", features = ["wasmbind"] }

30
example/demo/README.md Normal file
View File

@ -0,0 +1,30 @@
# Demo Single Page Application for the OIDC IdP
This demo's purpose is to display the claims that are shared with Relying
Parties. It is currently deployed at https://demo-oidc.login.xyz.
## Dependencies
```sh
$ cargo install trunk
$ rustup target add wasm32-unknown-unknown
```
## Development
```sh
trunk serve --open
```
## Deploy
```sh
cp wrangler_example.toml wrangler.toml
```
And fill in `account_id` and `zone_id`.
```sh
$ source .env
$ trunk build
$ wrangler publish
```

10
example/demo/index.html Normal file
View File

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
</head>
<body>
</body>
</html>

122
example/demo/src/main.rs Normal file
View File

@ -0,0 +1,122 @@
use openidconnect::{
core::{CoreAuthenticationFlow, CoreClient, CoreProviderMetadata},
reqwest::async_http_client,
AuthorizationCode, ClientId, ClientSecret, CsrfToken, IssuerUrl, Nonce, Scope, TokenResponse,
};
use serde::Deserialize;
use url::Url;
use wasm_bindgen_futures::spawn_local;
use yew::prelude::*;
use yew_router::{hooks::use_location, prelude::*};
lazy_static::lazy_static! {
static ref CLIENT_ID: String = option_env!("CLIENT_ID").unwrap_or("fb24a7d9-6db9-476b-93c4-e8562e750250").to_string();
static ref CLIENT_SECRET: String = option_env!("CLIENT_SECRET").unwrap_or("6aae6334-148f-464c-a4bf-a204e62e197c").to_string();
static ref REDIRECT_URI: Url = Url::parse(option_env!("REDIRECT_URI").unwrap_or("http://localhost:8080/callback")).unwrap();
}
#[derive(Clone, Routable, PartialEq)]
enum Route {
#[at("/")]
Home,
#[at("/callback")]
OIDCCallback,
#[not_found]
#[at("/404")]
NotFound,
}
#[function_component(SIWE)]
pub fn siwe() -> Html {
html! {
<>
<form action="https://oidc.login.xyz/authorize">
<input type="hidden" name="client_id" value={ CLIENT_ID.clone() } />
<input type="hidden" name="response_type" value="code" />
<input type="hidden" name="nonce" value="nonce" />
<input type="hidden" name="scope" value="openid profile" />
<input type="hidden" name="state" value="state" />
<input type="hidden" name="redirect_uri" value={ REDIRECT_URI.to_string() } />
<input type="submit" value="Sign-In with Ethereum using OpenID Connect" />
</form>
</>
}
}
#[derive(Deserialize)]
struct CallbackParams {
code: String,
_state: String,
}
#[function_component(Callback)]
pub fn callback() -> Html {
let location = use_location().unwrap();
let params: CallbackParams = location.query().unwrap();
let claims = use_state(String::default);
let claims2 = claims.clone();
spawn_local(async move {
let provider_metadata = CoreProviderMetadata::discover_async(
IssuerUrl::new("https://oidc.login.xyz/".to_string()).unwrap(),
async_http_client,
)
.await
.unwrap();
let client = CoreClient::from_provider_metadata(
provider_metadata,
ClientId::new(CLIENT_ID.to_string()),
Some(ClientSecret::new(CLIENT_SECRET.to_string())),
);
let (_auth_url, _csrf_token, nonce) = client
.authorize_url(
CoreAuthenticationFlow::AuthorizationCode,
CsrfToken::new_random,
|| Nonce::new("nonce".to_string()),
)
.add_scope(Scope::new("openid".to_string()))
.add_scope(Scope::new("profile".to_string()))
.url();
let token_response = client
.exchange_code(AuthorizationCode::new(params.code))
.request_async(async_http_client)
.await
.unwrap();
let id_token = token_response.id_token().unwrap();
claims2.set(
serde_json::to_string(
id_token
.claims(&client.id_token_verifier(), &nonce)
.unwrap(),
)
.unwrap(),
);
});
html! {
<>
<p>{ (*claims).clone() }</p>
</>
}
}
fn switch(routes: &Route) -> Html {
match routes {
Route::Home => html! { <SIWE /> },
Route::OIDCCallback => html! { <Callback /> },
Route::NotFound => html! { <Redirect<Route> to={Route::Home}/> },
}
}
#[function_component(App)]
fn app() -> Html {
html! {
<BrowserRouter>
<Switch<Route> render={Switch::render(switch)} />
</BrowserRouter>
}
}
fn main() {
yew::start_app::<App>();
}

3
example/demo/workers-site/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
node_modules
dist
worker

View File

@ -0,0 +1,39 @@
import { getAssetFromKV, mapRequestToAsset } from '@cloudflare/kv-asset-handler'
const DEBUG = false
addEventListener('fetch', event => {
event.respondWith(handleEvent(event))
})
async function handleEvent(event) {
let options = {}
options.mapRequestToAsset = spaRouting()
options.cacheControl = {
bypassCache: DEBUG,
}
try {
const page = await getAssetFromKV(event, options)
const response = new Response(page.body, page)
response.headers.set('X-XSS-Protection', '1; mode=block')
response.headers.set('X-Content-Type-Options', 'nosniff')
response.headers.set('X-Frame-Options', 'DENY')
response.headers.set('Referrer-Policy', 'unsafe-url')
response.headers.set('Feature-Policy', 'none')
return response
} catch (e) {
return new Response(e.message || e.toString(), { status: 500 })
}
}
function spaRouting() {
return request => {
let defaultAssetKey = mapRequestToAsset(request)
let url = new URL(defaultAssetKey.url)
if (url.pathname.includes(".html")) {
url.pathname = "/index.html"
}
return new Request(url.toString(), defaultAssetKey)
}
}

View File

@ -0,0 +1,10 @@
{
"private": true,
"version": "1.0.0",
"description": "A template for kick starting a Cloudflare Workers project",
"main": "index.js",
"license": "MIT",
"dependencies": {
"@cloudflare/kv-asset-handler": "~0.1.2"
}
}

View File

@ -0,0 +1,19 @@
name = "siwe-oidc-demo"
type = "webpack"
account_id = ""
workers_dev = false
zone_id = ""
routes = ["demo-oidc.login.xyz/*"]
compatibility_date = "2022-02-10"
[build]
command = "cargo install -q trunk && trunk build"
[build.upload]
format = "service-worker"
[site]
bucket = "./dist"