diff --git a/Cargo.lock b/Cargo.lock index ccbe251..87c1443 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2581,7 +2581,7 @@ dependencies = [ [[package]] name = "rustpad" -version = "0.1.13" +version = "0.1.14" dependencies = [ "argon2", "aws-config", diff --git a/Cargo.toml b/Cargo.toml index 48a55c7..a57e4ef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rustpad" -version = "0.1.13" +version = "0.1.14" edition = "2024" rust-version = "1.94" description = "Collaborative Markdown notepad built with Axum, WebSockets and SQLite, PostgreSQL and MySQL" diff --git a/src/app/mod.rs b/src/app/mod.rs index 5839c99..51cabbe 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -103,7 +103,7 @@ pub fn router( ) .route( "/share-invitations/{token}/accept", - get(auth::accept_share_invitation), + get(home).post(auth::accept_share_invitation), ) .route("/api/auth/password-reset", post(auth::request_reset)) .route( diff --git a/src/auth/mod.rs b/src/auth/mod.rs index 11787be..24d9324 100644 --- a/src/auth/mod.rs +++ b/src/auth/mod.rs @@ -9,7 +9,6 @@ use axum::{ Json, extract::{Path as AxumPath, State}, http::{HeaderMap, StatusCode}, - response::Redirect, }; use chrono::{Duration, Utc}; use lettre::{ @@ -1261,7 +1260,7 @@ pub async fn share_resource_users( pub async fn accept_share_invitation( State(state): State, AxumPath(token): AxumPath, -) -> Result { +) -> Result, AuthError> { let token_hash = hash_token(token.trim()); let row: Option<(String, String, i64, String, String, Option)> = sqlx::query_as( queries::get(state.db.kind(), queries::SHARE_INVITATION_FIND_TOKEN), @@ -1320,7 +1319,7 @@ pub async fn accept_share_invitation( } else { format!("/p/{slug}") }; - Ok(Redirect::to(&target)) + Ok(Json(serde_json::json!({"ok": true, "url": target}))) } pub async fn remove_resource_user( diff --git a/static/js/auth-ui.js b/static/js/auth-ui.js index 2c7aa67..95a7dae 100644 --- a/static/js/auth-ui.js +++ b/static/js/auth-ui.js @@ -344,6 +344,11 @@ export async function handleAccountConfirmationToken() { const token = mailActionToken("/auth/confirm", "confirm_token"); if (!token) return false; clearMailActionUrl(); + const confirmed = await askConfirm( + "Confirm activation of this RustPad account.", + { title: "Confirm account", confirmText: "Activate account" }, + ); + if (!confirmed) return true; try { const result = await api("/api/auth/confirm-account", { method: "POST", body: JSON.stringify({ token }) }); await showMessage(result.message, { title: "Account confirmed" }); @@ -378,6 +383,25 @@ export async function handleResetToken() { return true; } +export async function handleShareInvitationToken() { + const token = mailActionToken("/share-invitations", "share_invitation_token"); + if (!token || !location.pathname.endsWith("/accept")) return false; + clearMailActionUrl(); + + const confirmed = await askConfirm( + "Accept this sharing invitation?", + { title: "Sharing invitation", confirmText: "Accept invitation" }, + ); + if (!confirmed) return true; + try { + const result = await api(`/share-invitations/${encodeURIComponent(token)}/accept`, { method: "POST" }); + window.location.assign(result.url); + } catch (error) { + await showMessage(error.message, { title: "Invitation failed" }); + } + return true; +} + export async function handleAccountActionToken() { const token = mailActionToken("/auth/account-action", "account_action_token"); if (!token) return false; diff --git a/static/js/home.js b/static/js/home.js index 018b050..c8e1894 100644 --- a/static/js/home.js +++ b/static/js/home.js @@ -1,7 +1,7 @@ import { installGlobalDiagnostics, logInfo } from "@rustpad/logger"; installGlobalDiagnostics(); -import { bindIdentityDialog, handleAccountActionToken, handleAccountConfirmationToken, handleResetToken, logoutCurrentSession, validateCurrentSession } from "@rustpad/auth-ui"; +import { bindIdentityDialog, handleAccountActionToken, handleAccountConfirmationToken, handleResetToken, handleShareInvitationToken, logoutCurrentSession, validateCurrentSession } from "@rustpad/auth-ui"; import { getAuthToken, setAccessToken } from "@rustpad/session"; import { api } from "@rustpad/api"; import { copyText } from "@rustpad/clipboard"; @@ -83,7 +83,7 @@ document.querySelector("#workspace-form").addEventListener("submit", async (even } }); -await handleAccountConfirmationToken() || await handleResetToken() || await handleAccountActionToken(); +await handleAccountConfirmationToken() || await handleResetToken() || await handleAccountActionToken() || await handleShareInvitationToken(); const identityDialog = document.querySelector("#identity-dialog"); const guestAccount = document.querySelector("#footer-account-guest");