From 66049566ae865e1a4bd049257d6afc0abded16e9 Mon Sep 17 00:00:00 2001 From: Vika Date: Mon, 19 Sep 2022 17:30:38 +0300 Subject: feat: indieauth support Working: - Tokens and codes - Authenticating with a password Not working: - Setting the password (need to patch onboarding) - WebAuthn (the JavaScript is too complicated) --- kittybox-rs/templates/src/indieauth.rs | 174 ++++++++++++++++++++++++++++++++- kittybox-rs/templates/src/templates.rs | 4 +- 2 files changed, 174 insertions(+), 4 deletions(-) (limited to 'kittybox-rs/templates') diff --git a/kittybox-rs/templates/src/indieauth.rs b/kittybox-rs/templates/src/indieauth.rs index 99e94c7..23f64e9 100644 --- a/kittybox-rs/templates/src/indieauth.rs +++ b/kittybox-rs/templates/src/indieauth.rs @@ -1,7 +1,175 @@ -use kittybox_indieauth::AuthorizationRequest; +use kittybox_indieauth::{AuthorizationRequest, Scope}; +use kittybox_util::auth::EnrolledCredential; markup::define! { - AuthorizationRequestPage() { - + AuthorizationRequestPage( + request: AuthorizationRequest, + credentials: Vec, + app: serde_json::Value, + user: serde_json::Value + ) { + script[src="/.kittybox/static/indieauth.js", type="module"] {} + 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["properties"]["logo"][0].as_str() { + img.app_icon[src=icon]; + } + span { + a[href=app["properties"]["url"][0].as_str().unwrap()] { + @app["properties"]["name"][0].as_str().unwrap() + } + " 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()]; + } + } + } + } + } } } diff --git a/kittybox-rs/templates/src/templates.rs b/kittybox-rs/templates/src/templates.rs index 60da6af..60daa55 100644 --- a/kittybox-rs/templates/src/templates.rs +++ b/kittybox-rs/templates/src/templates.rs @@ -16,7 +16,9 @@ markup::define! { link[rel="micropub", href="/.kittybox/micropub"]; link[rel="micropub_media", href="/.kittybox/media"]; link[rel="indieauth_metadata", href="/.kittybox/indieauth/metadata"]; - + // legacy links for some dumb clients + link[rel="authorization_endpoint", href="/.kittybox/indieauth/auth"]; + link[rel="token_endpoint", href="/.kittybox/indieauth/token"]; /*@if let Some(endpoints) = endpoints { @if let Some(webmention) = &endpoints.webmention { link[rel="webmention", href=&webmention]; -- cgit 1.4.1