Skip to content

Commit

Permalink
feat: minimal theme and inline editing (#93)
Browse files Browse the repository at this point in the history
Added the first theme for the public page, a `minimal` theme, including initial inline editing support.

---------

Co-authored-by: Zicklag <[email protected]>
  • Loading branch information
kimlimjustin and zicklag authored Jul 17, 2024
1 parent 22b734d commit f3f2eea
Show file tree
Hide file tree
Showing 17 changed files with 536 additions and 146 deletions.
47 changes: 35 additions & 12 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion backend/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ anyhow = "1.0.86"
async-graphql = "7.0.5"
async-graphql-axum = "7.0.5"
async-trait = "0.1.80"
axum = { version = "0.7.5", features = ["multipart"] }
axum = { version = "0.7.5", features = ["multipart", "macros"] }
axum-extra = { version = "0.9.3", features = ["cookie"] }
bytes = "1.6.0"
clap = { version = "4.5.4", features = ["derive", "env"] }
Expand All @@ -20,6 +20,7 @@ once_cell = "1.19.0"
quic-rpc = "0.10.1"
rand = "0.8.5"
reqwest = { version = "0.12.4", features = ["json"], default-features = false }
scc = "2.1.2"
serde = { version = "1.0.202", features = ["derive"] }
serde_json = "1.0.117"
serde_yaml = "0.9.34"
Expand Down
88 changes: 88 additions & 0 deletions backend/src/routes/profile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ use axum::{
routing::delete,
};
use futures::{pin_mut, StreamExt};
use http::HeaderMap;
use once_cell::sync::Lazy;
use rand::{distributions::Alphanumeric, Rng};
use scc::HashMap as SccMap;
use serde::{Deserialize, Serialize};
use weird::{
gdata::{GStoreBackend, Key},
Expand All @@ -28,6 +31,12 @@ pub struct ProfileWithDomain {
}

static DOMAINS_STORAGE_KEY: Lazy<Key> = Lazy::new(|| Key::from(["weird_backend", "v1", "domains"]));
static USER_TOKENS: Lazy<SccMap<String, UserToken>> = Lazy::new(SccMap::new);
#[derive(Debug)]
struct UserToken {
token: String,
user_id: String,
}

pub fn install(router: Router<AppState>) -> Router<AppState> {
router
Expand All @@ -36,6 +45,7 @@ pub fn install(router: Router<AppState>) -> Router<AppState> {
.route("/profile/username/:username", get(get_profile_by_name))
.route("/profile/domain/:domain", get(get_profile_by_domain))
.route("/profile/domain/:domain", post(set_domain_for_profile))
.route("/profile/by-token/:domain", post(post_profile_by_token))
.route(
"/profile/username/:username/avatar",
get(get_profile_avatar_by_name),
Expand All @@ -48,6 +58,41 @@ pub fn install(router: Router<AppState>) -> Router<AppState> {
.route("/profile/:user_id", get(get_profile))
.route("/profile/:user_id", post(post_profile))
.route("/profile/:user_id", delete(delete_profile))
.route("/token/:domain/revoke", post(post_revoke_token))
.route(
"/token/:domain/generate/:user_id",
post(post_generate_token),
)
.route("/token/:domain/verify", post(post_verify_token))
}

async fn post_revoke_token(Path(user_id): Path<String>) -> AppResult<()> {
let _ = USER_TOKENS.remove(&user_id);
Ok(())
}

#[derive(serde::Serialize, serde::Deserialize)]
struct Token {
token: String,
}

async fn post_generate_token(
Path((domain, user_id)): Path<(String, String)>,
) -> AppResult<Json<Token>> {
let token: String = rand::thread_rng()
.sample_iter(&Alphanumeric)
.take(12)
.map(char::from)
.collect();
USER_TOKENS.remove(&domain);
let _ = USER_TOKENS.insert(
domain.clone(),
UserToken {
token: token.clone(),
user_id,
},
);
Ok(Json(Token { token }))
}

async fn get_domains(state: State<AppState>) -> AppResult<Json<Vec<String>>> {
Expand Down Expand Up @@ -217,6 +262,49 @@ async fn post_profile(
) -> AppResult<()> {
let author = state.weird.get_or_init_author(user_id).await?;
state.weird.set_profile(author, new_profile.0).await?;
Ok(())
}

async fn post_verify_token(Path(domain): Path<String>, headers: HeaderMap) -> AppResult<()> {
let token = headers
.get("x-token-auth")
.ok_or_else(|| anyhow::format_err!("x-token-auth header not provided"))?;
let token = token.to_str()?;

let is_verified = match USER_TOKENS.get(&domain) {
Some(t) => t.token == token,
None => false,
};
if is_verified {
Ok(())
} else {
Err(anyhow::format_err!("Token is invalid").into())
}
}

async fn post_profile_by_token(
state: State<AppState>,
Path(domain): Path<String>,
headers: HeaderMap,
new_profile: Json<Profile>,
) -> AppResult<()> {
let token = headers
.get("x-token-auth")
.ok_or_else(|| anyhow::format_err!("x-token-auth header not provided"))?;
let token = token.to_str()?;

let stored_token = USER_TOKENS
.get(&domain)
.ok_or_else(|| anyhow::format_err!("Invalid token."))?;
if stored_token.token != token {
return Err(anyhow::format_err!("Invalid token.").into());
}

let author = state
.weird
.get_or_init_author(&stored_token.user_id)
.await?;
state.weird.set_profile(author, new_profile.0).await?;

Ok(())
}
Expand Down
6 changes: 5 additions & 1 deletion src/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ const subdomainRegex = new RegExp(
);

export const reroute: Reroute = ({ url }) => {
if (url.host == env.PUBLIC_DOMAIN || url.pathname.startsWith('/dns-challenge')) {
if (
url.host == env.PUBLIC_DOMAIN ||
url.pathname.startsWith('/dns-challenge') ||
url.pathname.startsWith('/pubpage-auth-callback')
) {
return url.pathname;
}

Expand Down
Loading

0 comments on commit f3f2eea

Please sign in to comment.