Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: custom domains using Fairings #596

Open
wants to merge 48 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 34 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
6bcc4f0
add custom_domain (default to NULL) to schema
igalic May 22, 2019
92bbeeb
add Host(String) wrapper type
igalic May 24, 2019
65ae51b
implement Host more completely by doing less
igalic May 25, 2019
351c01f
list custom_domains
igalic May 25, 2019
3a4c2f2
create & attach an AdHoc Fairing for dealing with Custom Domains
igalic May 26, 2019
2746e08
appease clippy
igalic May 26, 2019
e6747de
cache custom_domains list
igalic May 26, 2019
b09b51c
normalize URLs before setting them
igalic May 27, 2019
f94b0c7
move impl closer to mother struct
igalic May 27, 2019
0645f7e
CustomDomainFairing must not match /static/ and /api/
igalic May 27, 2019
468e663
Add custom_details and custom_activity_details as first routes
igalic May 27, 2019
1c34ac3
replace if / else with ok_or()
igalic May 27, 2019
8e6b1ab
simplify retrieval in find_by_host()
igalic May 28, 2019
a0aef50
extract common routing code into private "_guts()" functions
igalic May 28, 2019
6253adf
simplify / unify error handling
igalic May 28, 2019
92fbd17
extend 404 handler to handle all the requests our custom_ routes dont
igalic May 28, 2019
8e7f789
Allow searching from custom_domain
igalic May 30, 2019
9cee38a
add url to Blog, this seems useful.
igalic May 31, 2019
cc0df4e
redirect blog urls to custom_domain if it exists
igalic Jun 14, 2019
fe110b5
fix issues pointed out by @BaptisteGelez in review
igalic Jun 15, 2019
f73fba5
custom_domainify posts::details
igalic Jul 10, 2019
203da23
follow up on @fdb-hiroshima & @BaptisteGelez review
igalic Jul 12, 2019
7139119
add url! macro for custom domain path uri creation
trinity-1686a Jul 14, 2019
f635dcf
move custom_ route functions into a custom namespace
igalic Jul 14, 2019
cdc919e
allow more syntaxes for url
trinity-1686a Jul 14, 2019
6cd8bd8
fix syntax: we're now down to type errors
igalic Jul 21, 2019
c5f6b88
make url! work better
trinity-1686a Jul 22, 2019
b172a80
finally fix url!
trinity-1686a Jul 22, 2019
2dedcdb
start modifying /blogs/new to add custom_domain to the form
igalic Jul 30, 2019
fdc7da0
Strings have to be cloned
elegaanz Jul 31, 2019
6027012
fix clippy warning
igalic Jul 31, 2019
da6757c
Basic domain validation
elegaanz Jul 31, 2019
ff0c82e
review by @fdb-hiroshima
igalic Aug 1, 2019
bf1673d
start exploring some ideas for domain validation
igalic Aug 14, 2019
ed30284
implement domain validation using rocket::State
igalic Aug 15, 2019
444a467
correctly mutex-lock and modify valid_domains
igalic Aug 19, 2019
6072351
one step closer to validating custom domains!
igalic Aug 19, 2019
5e46922
validate custom domain!
igalic Aug 19, 2019
38ece9b
do not panic if reqwest does not work
igalic Aug 20, 2019
f67ce93
fix getter router for domain_validation!
igalic Aug 20, 2019
df47cdd
also mount domain_validation into /custom_domains/
igalic Aug 20, 2019
064dd79
Allow editing blogs with custom_domain
igalic Aug 20, 2019
1694698
use Custom status code
igalic Aug 20, 2019
768f126
use Status::* as @fdb-hiroshima suggested
igalic Aug 20, 2019
df7adaf
when building links, skip both, /custom_domain/ and <custom_domain>
igalic Aug 20, 2019
f395c10
fix mount point for blog post routing function
igalic Aug 20, 2019
d242e6d
restore df7adaf0e067802daf3e83935cf9140fb15ca17b ← after fixing mount…
igalic Aug 20, 2019
3c4abcf
Allow updating of custom_domain
igalic Aug 21, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- undo the adding of custom_domain column to blogs table.
ALTER TABLE blogs DROP COLUMN custom_domain;
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
--- Adding custom domain to Blog as an optional field
ALTER TABLE blogs ADD COLUMN custom_domain VARCHAR DEFAULT NULL UNIQUE;
56 changes: 56 additions & 0 deletions migrations/sqlite/2019-05-22-124153_add_custom_domains/down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
-- undo the adding of "custom_domain" to blogs
CREATE TABLE IF NOT EXISTS "blogs_drop_custom_domain" (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
actor_id VARCHAR NOT NULL,
title VARCHAR NOT NULL,
summary TEXT NOT NULL DEFAULT '',
outbox_url VARCHAR NOT NULL UNIQUE,
inbox_url VARCHAR NOT NULL UNIQUE,
instance_id INTEGER REFERENCES instances(id) ON DELETE CASCADE NOT NULL,
creation_date DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
ap_url text not null default '' UNIQUE,
private_key TEXT,
public_key TEXT NOT NULL DEFAULT '',
fqn TEXT NOT NULL DEFAULT '',
summary_html TEXT NOT NULL DEFAULT '',
icon_id INTEGER REFERENCES medias(id) ON DELETE SET NULL DEFAULT NULL,
banner_id INTEGER REFERENCES medias(id) ON DELETE SET NULL DEFAULT NULL,
CONSTRAINT blog_unique UNIQUE (actor_id, instance_id)
);

INSERT INTO blogs_drop_custom_domain (
id,
actor_id,
title,
summary,
outbox_url,
inbox_url,
instance_id,
creation_date,
ap_url,
private_key,
public_key,
fqn,
summary_html,
icon_id,
banner_id
) SELECT
id,
actor_id,
title,
summary,
outbox_url,
inbox_url,
instance_id,
creation_date,
ap_url,
private_key,
public_key,
fqn,
summary_html,
icon_id,
banner_id
FROM blogs;

DROP TABLE blogs;
ALTER TABLE "blogs_drop_custom_domain" RENAME to blogs;
57 changes: 57 additions & 0 deletions migrations/sqlite/2019-05-22-124153_add_custom_domains/up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
-- add custom_domain to blogs
CREATE TABLE IF NOT EXISTS "blogs_add_custom_domain" (
igalic marked this conversation as resolved.
Show resolved Hide resolved
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
actor_id VARCHAR NOT NULL,
title VARCHAR NOT NULL,
summary TEXT NOT NULL DEFAULT '',
outbox_url VARCHAR NOT NULL UNIQUE,
inbox_url VARCHAR NOT NULL UNIQUE,
instance_id INTEGER REFERENCES instances(id) ON DELETE CASCADE NOT NULL,
creation_date DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
ap_url text not null default '' UNIQUE,
private_key TEXT,
public_key TEXT NOT NULL DEFAULT '',
fqn TEXT NOT NULL DEFAULT '',
summary_html TEXT NOT NULL DEFAULT '',
icon_id INTEGER REFERENCES medias(id) ON DELETE SET NULL DEFAULT NULL,
banner_id INTEGER REFERENCES medias(id) ON DELETE SET NULL DEFAULT NULL,
custom_domain text default NULL UNIQUE,
CONSTRAINT blog_unique UNIQUE (actor_id, instance_id)
);

INSERT INTO blogs_add_custom_domain (
id,
actor_id,
title,
summary,
outbox_url,
inbox_url,
instance_id,
creation_date,
ap_url,
private_key,
public_key,
fqn,
summary_html,
icon_id,
banner_id
) SELECT
id,
actor_id,
title,
summary,
outbox_url,
inbox_url,
instance_id,
creation_date,
ap_url,
private_key,
public_key,
fqn,
summary_html,
icon_id,
banner_id
FROM blogs;

DROP TABLE blogs;
ALTER TABLE "blogs_add_custom_domain" RENAME to blogs;
101 changes: 101 additions & 0 deletions plume-models/src/blogs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ use openssl::{
rsa::Rsa,
sign::{Signer, Verifier},
};
use rocket::{
outcome::IntoOutcome,
request::{self, FromRequest, Request},
};
use serde_json;
use url::Url;
use webfinger::*;
Expand All @@ -21,11 +25,52 @@ use posts::Post;
use safe_string::SafeString;
use schema::blogs;
use search::Searcher;
use std::fmt::{self, Display};
use std::sync::RwLock;
use users::User;
use {Connection, Error, PlumeRocket, Result};

pub type CustomGroup = CustomObject<ApSignature, Group>;

#[derive(Clone, Debug, PartialEq, DieselNewType, Shrinkwrap)]
pub struct Host(String);

impl Host {
pub fn new(host: impl ToString) -> Host {
Host(host.to_string())
}
}

impl Display for Host {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}

impl AsRef<str> for Host {
fn as_ref(&self) -> &str {
&self.0
}
}

impl<'a, 'r> FromRequest<'a, 'r> for Host {
type Error = ();

fn from_request(request: &'a Request<'r>) -> request::Outcome<Host, ()> {
request
.headers()
.get_one("Host")
.and_then(|x| {
if Blog::list_custom_domains().contains(&x.to_string()) {
Some(Host::new(x))
} else {
None
}
})
.or_forward(())
}
}

#[derive(Queryable, Identifiable, Clone, AsChangeset)]
#[changeset_options(treat_none_as_null = "true")]
pub struct Blog {
Expand All @@ -44,6 +89,7 @@ pub struct Blog {
pub summary_html: SafeString,
pub icon_id: Option<i32>,
pub banner_id: Option<i32>,
pub custom_domain: Option<Host>,
}

#[derive(Default, Insertable)]
Expand All @@ -61,10 +107,15 @@ pub struct NewBlog {
pub summary_html: SafeString,
pub icon_id: Option<i32>,
pub banner_id: Option<i32>,
pub custom_domain: Option<Host>,
}

const BLOG_PREFIX: &str = "~";

lazy_static! {
static ref CUSTOM_DOMAINS: RwLock<Vec<String>> = RwLock::new(vec![]);
}

impl Blog {
insert!(blogs, NewBlog, |inserted, conn| {
let instance = inserted.get_instance(conn)?;
Expand Down Expand Up @@ -144,6 +195,13 @@ impl Blog {
}
}

pub fn find_by_host(c: &PlumeRocket, host: Host) -> Result<Blog> {
blogs::table
.filter(blogs::custom_domain.eq(host))
.first::<Blog>(&*c.conn)
igalic marked this conversation as resolved.
Show resolved Hide resolved
.map_err(|_| Error::NotFound)
}

fn fetch_from_webfinger(c: &PlumeRocket, acct: &str) -> Result<Blog> {
resolve_with_prefix(Prefix::Group, acct.to_owned(), true)?
.links
Expand Down Expand Up @@ -269,6 +327,19 @@ impl Blog {
})
}

pub fn url(&self) -> String {
format!(
"https://{}",
self.custom_domain
.clone()
.unwrap_or_else(|| Host::new(format!(
"{}/~/{}",
Instance::get_local().unwrap().public_domain,
self.fqn,
)))
)
}

pub fn icon_url(&self, conn: &Connection) -> String {
self.icon_id
.and_then(|id| Media::get(conn, id).and_then(|m| m.url()).ok())
Expand All @@ -290,6 +361,23 @@ impl Blog {
.map(|_| ())
.map_err(Error::from)
}

pub fn list_custom_domains() -> Vec<String> {
CUSTOM_DOMAINS.read().unwrap().clone()
}

pub fn cache_custom_domains(conn: &Connection) {
*CUSTOM_DOMAINS.write().unwrap() = Blog::list_custom_domains_uncached(conn).unwrap();
}

pub fn list_custom_domains_uncached(conn: &Connection) -> Result<Vec<String>> {
blogs::table
.filter(blogs::custom_domain.is_not_null())
.select(blogs::custom_domain)
.load::<Option<String>>(conn)
.map_err(Error::from)
.map(|res| res.into_iter().map(Option::unwrap).collect::<Vec<_>>())
}
}

impl IntoId for Blog {
Expand Down Expand Up @@ -392,6 +480,7 @@ impl FromId<PlumeRocket> for Blog {
.summary_string()
.unwrap_or_default(),
),
custom_domain: None,
},
)
}
Expand Down Expand Up @@ -441,6 +530,7 @@ impl NewBlog {
title: String,
summary: String,
instance_id: i32,
custom_domain: Option<Host>,
) -> Result<NewBlog> {
let (pub_key, priv_key) = sign::gen_keypair();
Ok(NewBlog {
Expand All @@ -450,6 +540,7 @@ impl NewBlog {
instance_id,
public_key: String::from_utf8(pub_key).or(Err(Error::Signature))?,
private_key: Some(String::from_utf8(priv_key).or(Err(Error::Signature))?),
custom_domain,
..NewBlog::default()
})
}
Expand Down Expand Up @@ -477,6 +568,7 @@ pub(crate) mod tests {
"Blog name".to_owned(),
"This is a small blog".to_owned(),
Instance::get_local().unwrap().id,
None,
)
.unwrap(),
)
Expand All @@ -488,6 +580,7 @@ pub(crate) mod tests {
"My blog".to_owned(),
"Welcome to my blog".to_owned(),
Instance::get_local().unwrap().id,
Some(Host::new("blog.myname.me")),
)
.unwrap(),
)
Expand All @@ -499,6 +592,7 @@ pub(crate) mod tests {
"Why I like Plume".to_owned(),
"In this blog I will explay you why I like Plume so much".to_owned(),
Instance::get_local().unwrap().id,
None,
)
.unwrap(),
)
Expand Down Expand Up @@ -559,6 +653,7 @@ pub(crate) mod tests {
"Some name".to_owned(),
"This is some blog".to_owned(),
Instance::get_local().unwrap().id,
Some(Host::new("some.blog.com")),
)
.unwrap(),
)
Expand Down Expand Up @@ -587,6 +682,7 @@ pub(crate) mod tests {
"Some name".to_owned(),
"This is some blog".to_owned(),
Instance::get_local().unwrap().id,
None,
)
.unwrap(),
)
Expand All @@ -598,6 +694,7 @@ pub(crate) mod tests {
"Blog".to_owned(),
"I've named my blog Blog".to_owned(),
Instance::get_local().unwrap().id,
Some(Host::new("named.example.blog")),
)
.unwrap(),
)
Expand Down Expand Up @@ -690,6 +787,7 @@ pub(crate) mod tests {
"Some name".to_owned(),
"This is some blog".to_owned(),
Instance::get_local().unwrap().id,
None,
)
.unwrap(),
)
Expand All @@ -714,6 +812,7 @@ pub(crate) mod tests {
"Some name".to_owned(),
"This is some blog".to_owned(),
Instance::get_local().unwrap().id,
Some(Host::new("some.blog.com")),
)
.unwrap(),
)
Expand Down Expand Up @@ -752,6 +851,7 @@ pub(crate) mod tests {
"Some name".to_owned(),
"This is some blog".to_owned(),
Instance::get_local().unwrap().id,
None,
)
.unwrap(),
)
Expand All @@ -763,6 +863,7 @@ pub(crate) mod tests {
"Blog".to_owned(),
"I've named my blog Blog".to_owned(),
Instance::get_local().unwrap().id,
Some(Host::new("my.blog.com")),
)
.unwrap(),
)
Expand Down
4 changes: 4 additions & 0 deletions plume-models/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ extern crate bcrypt;
extern crate chrono;
#[macro_use]
extern crate diesel;
#[macro_use]
extern crate diesel_derive_newtype;
extern crate guid_create;
extern crate heck;
extern crate itertools;
Expand All @@ -31,6 +33,8 @@ extern crate serde_derive;
#[macro_use]
extern crate serde_json;
#[macro_use]
extern crate shrinkwraprs;
#[macro_use]
extern crate tantivy;
extern crate url;
extern crate webfinger;
Expand Down
Loading