use kittybox_indieauth::{AuthorizationRequest, Scope}; use kittybox_util::auth::EnrolledCredential; markup::define! { AuthorizationRequestPage( request: AuthorizationRequest, credentials: Vec, app: Option, user: serde_json::Value ) { script[type="module"] { @markup::raw(r#"import { submit_handler } from "/.kittybox/static/indieauth.js"; document.getElementById("indieauth_page").addEventListener("submit", submit_handler); "#) } main { form #indieauth_page[action="/.kittybox/indieauth/auth/confirm", method="POST"] { noscript { p {"I know how annoyed you can be about scripts." } p { "But WebAuthn doesn't work without JavaScript. And passwords are horribly insecure, and everyone knows it deep inside their heart." } p { b { "Please enable JavaScript for this page to work properly 😭" } } } div #introduction { h1."mini-h-card" { "Hi, " @if let Some(photo) = user["properties"]["photo"][0].as_str() { img.user_avatar[src=photo]; } @user["properties"]["name"][0].as_str().unwrap_or("administrator") } p."mini-h-card" { @if let Some(icon) = app .as_ref() .and_then(|app| app["properties"]["logo"][0].as_str()) { img.app_icon[src=icon]; } else if let Some(icon) = app .as_ref() .and_then(|app| app["properties"]["logo"][0].as_object()) { img.app_icon[src=icon["src"].as_str().unwrap(), alt=icon["alt"].as_str().unwrap()]; } span { a[href=app .as_ref() .and_then(|app| app["properties"]["url"][0].as_str()) .unwrap_or_else(|| request.client_id.as_str()) ] { @app .as_ref() .and_then(|app| app["properties"]["name"][0].as_str()) .unwrap_or_else(|| request.client_id.as_str()) } " wants to confirm your identity." } } } @if request.scope.is_some() { p { "An application just requested access to your website. This can give access to your data, including private content." } p { "You can review the permissions the application requested below. You are free to not grant any permissions that the application requested if you don't trust it, at the cost of potentially reducing its functionality." } } fieldset #scopes { legend { "Permissions to grant the app:" } div { input[type="checkbox", disabled="true", checked="true"]; label[for="identify"] { "Identify you as the owner of " @user["properties"]["uid"][0].as_str().unwrap() } } @if let Some(scopes) = &request.scope { @for scope in scopes.iter() { div { input[type="checkbox", name="scope", id=scope.as_ref(), value=scope.as_ref()]; label[for=scope.as_ref()] { @match scope { Scope::Profile => { "Access your publicly visible profile information" } Scope::Email => { "Access your email address" } Scope::Create => { "Create new content on your website" } Scope::Update => { "Modify content on your website" } Scope::Delete => { "Delete content on your website" } Scope::Media => { "Interact with your media storage" } other => { @markup::raw(format!( "(custom or unknown scope) {}", other.as_ref() )) } } } } } } } fieldset { legend { "Choose your preferred authentication method:" } div { input[type="radio", name="auth_method", id="auth_with_webauthn", disabled=!credentials.iter().any(|e| *e == EnrolledCredential::WebAuthn), checked=credentials.iter().any(|e| *e == EnrolledCredential::WebAuthn) ]; label[for="auth_with_webauthn"] { "Use an authenticator device to log in" } } div { input[type="radio", name="auth_method", value="password", id="auth_with_password", disabled=!credentials.iter().any(|e| *e == EnrolledCredential::Password), checked=credentials.iter().all(|e| *e == EnrolledCredential::Password) ]; label[for="auth_with_password"] { "Password" } br; input[type="password", name="user_password", id="user_password"]; } } input[type="submit", value="Authenticate"]; br; details { summary { "View detailed data about this request" } p { "More info about meanings of these fields can be found in " a[href="https://indieauth.spec.indieweb.org/20220212/#authorization-request"] { "the IndieAuth specification" } ", which this webpage uses." } fieldset { div { label[for="response_type"] { "Response type (will most likely be \"code\")" } br; input[name="response_type", id="response_type", readonly, value=request.response_type.as_str()]; } div { label[for="state"] { "Request state" } br; input[name="state", id="state", readonly, value=request.state.as_ref()]; } div { label[for="client_id"] { "Client ID" } br; input[name="client_id", id="client_id", readonly, value=request.client_id.as_str()]; } div { label[for="redirect_uri"] { "Redirect URI" } br; input[name="redirect_uri", id="redirect_uri", readonly, value=request.redirect_uri.as_str()]; } div { label[for="code_challenge"] { "PKCE code challenge" } br; input[name="code_challenge", id="code_challenge", readonly, value=request.code_challenge.as_str()]; } div { label[for="code_challenge_method"] { "PKCE method (should be S256)" } br; input[name="code_challenge_method", id="code_challenge_method", readonly, value=request.code_challenge.method().as_str()]; } } } } } } }