feat: use fixed value for an instance for DNS challenge

DESCRIPTION
    Randomly generated values for forge ownership verification through
    DNS TXT records was making development process complicated. Using
    starchart instance's hostname for the TXT record's value is secure
    enough for our use case.

    This patch gets rid of all the code necessary to implement random
    value challenges
This commit is contained in:
Aravinth Manivannan 2023-02-22 12:05:49 +05:30
parent 38eb9c74ec
commit e726f2234d
Signed by: realaravinth
GPG key ID: AD9F0F08E855ED88
6 changed files with 162 additions and 334 deletions

View file

@ -168,17 +168,6 @@ pub struct Repository {
pub import: bool,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
/// represents a DNS challenge
pub struct Challenge {
/// url of the forge instance
pub url: String,
/// key of TXT record
pub key: String,
/// value of TXT record
pub value: String,
}
#[async_trait]
/// Starchart's database requirements. To implement support for $Database, kindly implement this
/// trait.
@ -186,18 +175,6 @@ pub trait SCDatabase: std::marker::Send + std::marker::Sync + CloneSPDatabase {
/// ping DB
async fn ping(&self) -> bool;
/// check if a DNS challenge exists
async fn dns_challenge_exists(&self, key: &str) -> DBResult<bool>;
/// create DNS challenge
async fn create_dns_challenge(&self, challenge: &Challenge) -> DBResult<()>;
/// get DNS challenge
async fn get_dns_challenge(&self, key: &str) -> DBResult<Challenge>;
/// delete DNS challenge
async fn delete_dns_challenge(&self, key: &str) -> DBResult<()>;
/// create forge instance
async fn create_forge_instance(&self, f: &CreateForge) -> DBResult<()>;

View file

@ -18,6 +18,108 @@
},
"query": "SELECT ID FROM starchart_forges WHERE hostname = $1"
},
"0fbcc736f60b14d55fbd88031a2929d04de02f5244345c2bc0f0e58d4c29cb14": {
"describe": {
"columns": [
{
"name": "html_url",
"ordinal": 0,
"type_info": "Text"
},
{
"name": "profile_photo_html_url",
"ordinal": 1,
"type_info": "Text"
},
{
"name": "imported",
"ordinal": 2,
"type_info": "Bool"
}
],
"nullable": [
false,
true,
false
],
"parameters": {
"Right": 2
}
},
"query": "SELECT html_url, profile_photo_html_url, imported FROM starchart_users WHERE username = $1 AND \n hostname_id = (SELECT ID FROM starchart_forges WHERE hostname = $2)"
},
"1b88e5b97fd62fe5036ea3c87c90315f8d368542a8f2e12a0d542d1317a612db": {
"describe": {
"columns": [
{
"name": "hostname",
"ordinal": 0,
"type_info": "Text"
},
{
"name": "last_crawl_on",
"ordinal": 1,
"type_info": "Int64"
},
{
"name": "name",
"ordinal": 2,
"type_info": "Text"
},
{
"name": "imported",
"ordinal": 3,
"type_info": "Bool"
}
],
"nullable": [
false,
true,
false,
false
],
"parameters": {
"Right": 2
}
},
"query": "SELECT\n hostname,\n last_crawl_on,\n starchart_forge_type.name,\n imported\n FROM\n starchart_forges\n INNER JOIN\n starchart_forge_type\n ON\n starchart_forges.forge_type = starchart_forge_type.id\n ORDER BY\n starchart_forges.ID\n LIMIT $1 OFFSET $2;\n "
},
"1d98f7afc772f43fb7131131517f831ec9add25ec3f66881143d6d9baa9e2cc6": {
"describe": {
"columns": [
{
"name": "hostname",
"ordinal": 0,
"type_info": "Text"
},
{
"name": "last_crawl_on",
"ordinal": 1,
"type_info": "Int64"
},
{
"name": "imported",
"ordinal": 2,
"type_info": "Bool"
},
{
"name": "name",
"ordinal": 3,
"type_info": "Text"
}
],
"nullable": [
false,
true,
false,
false
],
"parameters": {
"Right": 1
}
},
"query": "SELECT \n hostname,\n last_crawl_on,\n imported,\n starchart_forge_type.name\n FROM\n starchart_forges\n INNER JOIN\n starchart_forge_type\n ON\n starchart_forges.forge_type = starchart_forge_type.id\n WHERE\n hostname = $1;\n "
},
"2afb17ba3753aa440465a836b46b7a1466f25791cfc4d0acdd38bc2755ae3e86": {
"describe": {
"columns": [
@ -54,6 +156,16 @@
},
"query": "SELECT ID FROM starchart_forge_type WHERE name = $1"
},
"338fb30307071e6df9efee6a68697c60e579d7b2332630bce401c0e7186a642a": {
"describe": {
"columns": [],
"nullable": [],
"parameters": {
"Right": 7
}
},
"query": "INSERT INTO \n starchart_users (\n hostname_id, username, html_url,\n profile_photo_html_url, added_on, last_crawl_on, imported\n ) \n VALUES (\n (SELECT ID FROM starchart_forges WHERE hostname = $1), $2, $3, $4, $5, $6, $7)"
},
"364c8e3d147318b864fd28ad284f225aaace9479b5cf0428fb97f0e5689e248d": {
"describe": {
"columns": [],
@ -92,17 +204,7 @@
},
"query": "SELECT ID FROM starchart_repositories\n WHERE \n name = $1\n AND\n owner_id = ( SELECT ID FROM starchart_users WHERE username = $2)\n AND\n hostname_id = (SELECT ID FROM starchart_forges WHERE hostname = $3)"
},
"76f49b3e5e0c6d16daeb09afca427cbf29cd477bd647fee04c2067f7f898721a": {
"describe": {
"columns": [],
"nullable": [],
"parameters": {
"Right": 1
}
},
"query": "DELETE FROM starchart_dns_challenges WHERE key = $1"
},
"80106a5fde0077375453d6170948330cc89dee997c2b8ad1d98b0799702eddc4": {
"78e53b067f8706f326fe06d184f8d94dd2c1869f89706e88afa9a0b19717229c": {
"describe": {
"columns": [
{
@ -139,6 +241,11 @@
"name": "website",
"ordinal": 6,
"type_info": "Text"
},
{
"name": "imported",
"ordinal": 7,
"type_info": "Bool"
}
],
"nullable": [
@ -148,85 +255,16 @@
true,
false,
false,
true
true,
false
],
"parameters": {
"Right": 2
}
},
"query": " SELECT \n\t\tstarchart_forges.hostname,\n\t\tstarchart_users.username,\n\t\tstarchart_repositories.name,\n\t\tstarchart_repositories.description,\n\t\tstarchart_repositories.html_url,\n starchart_repositories.ID,\n\t\tstarchart_repositories.website\nFROM\n\tstarchart_repositories\nINNER JOIN\n\tstarchart_forges\nON\n\tstarchart_repositories.hostname_id = starchart_forges.id\nINNER JOIN\n\tstarchart_users\nON\n\tstarchart_repositories.owner_id = starchart_users.id\nORDER BY\n\tstarchart_repositories.ID\nLIMIT $1 OFFSET $2\n;"
"query": "SELECT \n starchart_forges.hostname,\n starchart_users.username,\n starchart_repositories.name,\n starchart_repositories.description,\n starchart_repositories.html_url,\n starchart_repositories.ID,\n starchart_repositories.website,\n starchart_repositories.imported\n FROM\n starchart_repositories\n INNER JOIN\n starchart_forges\n ON\n starchart_repositories.hostname_id = starchart_forges.id\n INNER JOIN\n starchart_users\n ON\n starchart_repositories.owner_id = starchart_users.id\n ORDER BY\n starchart_repositories.ID\n LIMIT $1 OFFSET $2\n ;"
},
"891a09656a04fdabfbf51b15204e224221cea8d30782170d3d8503c9275b3d58": {
"describe": {
"columns": [
{
"name": "key",
"ordinal": 0,
"type_info": "Text"
},
{
"name": "value",
"ordinal": 1,
"type_info": "Text"
},
{
"name": "hostname",
"ordinal": 2,
"type_info": "Text"
}
],
"nullable": [
false,
false,
false
],
"parameters": {
"Right": 1
}
},
"query": "SELECT key, value, hostname FROM starchart_dns_challenges WHERE key = $1"
},
"8c78e074d78291f9d3c4ef3526bae00cb356591edad79a7fb1f20aa7bb681216": {
"describe": {
"columns": [],
"nullable": [],
"parameters": {
"Right": 3
}
},
"query": "INSERT INTO\n starchart_forges (hostname, verified_on, forge_type ) \n VALUES ($1, $2, (SELECT ID FROM starchart_forge_type WHERE name = $3))"
},
"a4e02e0fafe95a6e21347607f5f69a06f592c3624775feadaaf5fa5b4285815d": {
"describe": {
"columns": [
{
"name": "hostname",
"ordinal": 0,
"type_info": "Text"
},
{
"name": "last_crawl_on",
"ordinal": 1,
"type_info": "Int64"
},
{
"name": "name",
"ordinal": 2,
"type_info": "Text"
}
],
"nullable": [
false,
true,
false
],
"parameters": {
"Right": 1
}
},
"query": "SELECT \n\t\thostname,\n\t\tlast_crawl_on,\n\t\tstarchart_forge_type.name\n FROM\n starchart_forges\n INNER JOIN\n starchart_forge_type\n ON\n starchart_forges.forge_type = starchart_forge_type.id\n WHERE\n hostname = $1;\n "
},
"a75419ce6d1248944a7bdd63ddadd6f1017a27b8490fb2746a0f7a5d35ce889a": {
"9978a056397522cf1375900bb00c55bc17685dcc9cb22127b21a24b194a1e536": {
"describe": {
"columns": [],
"nullable": [],
@ -234,25 +272,7 @@
"Right": 4
}
},
"query": "INSERT INTO\n starchart_dns_challenges (hostname, value, key, created ) \n VALUES ($1, $2, $3, $4);"
},
"a77477b2f3c383c2c3e849e8aef47dab411f6c8f9cfe5cb6f850e28314eb1a47": {
"describe": {
"columns": [
{
"name": "ID",
"ordinal": 0,
"type_info": "Int64"
}
],
"nullable": [
false
],
"parameters": {
"Right": 1
}
},
"query": "SELECT ID FROM starchart_dns_challenges WHERE key = $1"
"query": "INSERT INTO\n starchart_forges (hostname, verified_on, forge_type, imported) \n VALUES ($1, $2, (SELECT ID FROM starchart_forge_type WHERE name = $3), $4)"
},
"a81dd4b5df666e22fac211092e7b8425d838dd9023aa2b17659352f30831944d": {
"describe": {
@ -272,55 +292,15 @@
},
"query": "SELECT ID FROM starchart_users WHERE username = $1 AND \n hostname_id = (SELECT ID FROM starchart_forges WHERE hostname = $2)"
},
"b4985ad11fafa367302ca9c0126b95bc70f6ae387f9de649aabb2ef424f676db": {
"ca22e5f6e7065cf2d4ffdbfac0084f9871de8cd9073d470cbf7eac2de2a73c47": {
"describe": {
"columns": [],
"nullable": [],
"parameters": {
"Right": 6
"Right": 9
}
},
"query": "INSERT INTO \n starchart_users (\n hostname_id, username, html_url,\n profile_photo_html_url, added_on, last_crawl_on\n ) \n VALUES (\n (SELECT ID FROM starchart_forges WHERE hostname = $1), $2, $3, $4, $5, $6)"
},
"c0439c4b2d683c516bd29780cd1e39a7bc75adaebdb450b864eb0b424f401b0c": {
"describe": {
"columns": [
{
"name": "hostname",
"ordinal": 0,
"type_info": "Text"
},
{
"name": "last_crawl_on",
"ordinal": 1,
"type_info": "Int64"
},
{
"name": "name",
"ordinal": 2,
"type_info": "Text"
}
],
"nullable": [
false,
true,
false
],
"parameters": {
"Right": 2
}
},
"query": "SELECT\n\t\thostname,\n\t\tlast_crawl_on,\n\t\tstarchart_forge_type.name\n FROM\n starchart_forges\n INNER JOIN\n starchart_forge_type\n ON\n starchart_forges.forge_type = starchart_forge_type.id\n ORDER BY\n starchart_forges.ID\n LIMIT $1 OFFSET $2;\n "
},
"e00c8a8b0dbeb4a89a673864055c137365c2ae7bc5daf677bdacb20f21d0fcb2": {
"describe": {
"columns": [],
"nullable": [],
"parameters": {
"Right": 8
}
},
"query": "INSERT INTO \n starchart_repositories (\n hostname_id, owner_id, name, description, html_url, website, created, last_crawl\n )\n VALUES (\n (SELECT ID FROM starchart_forges WHERE hostname = $1),\n (SELECT ID FROM starchart_users WHERE username = $2),\n $3, $4, $5, $6, $7, $8\n );"
"query": "INSERT INTO \n starchart_repositories (\n hostname_id, owner_id, name, description, html_url, website, created,\n last_crawl, imported\n )\n VALUES (\n (SELECT ID FROM starchart_forges WHERE hostname = $1),\n (SELECT ID FROM starchart_users WHERE username = $2),\n $3, $4, $5, $6, $7, $8, $9\n );"
},
"e30ccfaa6aeda8cf30a2b3e9134abd0c0420441c5ed05189c3be605b1405c8e9": {
"describe": {
@ -369,29 +349,5 @@
}
},
"query": "DELETE FROM starchart_forges WHERE hostname = ($1)"
},
"fbf6dd6bc2bc6121e080903fc9d6d9031b177acacf64fa92b7b52bd79f8fe89c": {
"describe": {
"columns": [
{
"name": "html_url",
"ordinal": 0,
"type_info": "Text"
},
{
"name": "profile_photo_html_url",
"ordinal": 1,
"type_info": "Text"
}
],
"nullable": [
false,
true
],
"parameters": {
"Right": 2
}
},
"query": "SELECT html_url, profile_photo_html_url FROM starchart_users WHERE username = $1 AND \n hostname_id = (SELECT ID FROM starchart_forges WHERE hostname = $2)"
}
}

View file

@ -439,69 +439,6 @@ impl SCDatabase for Database {
Ok(())
}
async fn dns_challenge_exists(&self, key: &str) -> DBResult<bool> {
match sqlx::query!(
"SELECT ID FROM starchart_dns_challenges WHERE key = $1",
key
)
.fetch_one(&self.pool)
.await
{
Ok(_) => Ok(true),
Err(Error::RowNotFound) => Ok(false),
Err(e) => Err(DBError::DBError(Box::new(e).into())),
}
}
async fn get_dns_challenge(&self, key: &str) -> DBResult<Challenge> {
struct InnerChallenge {
hostname: String,
key: String,
value: String,
}
let res = sqlx::query_as!(
InnerChallenge,
"SELECT key, value, hostname FROM starchart_dns_challenges WHERE key = $1",
key
)
.fetch_one(&self.pool)
.await
.map_err(|e| DBError::DBError(Box::new(e)))?;
let res = Challenge {
url: res.hostname,
key: res.key,
value: res.value,
};
Ok(res)
}
async fn delete_dns_challenge(&self, key: &str) -> DBResult<()> {
sqlx::query!("DELETE FROM starchart_dns_challenges WHERE key = $1", key)
.execute(&self.pool)
.await
.map_err(map_register_err)?;
Ok(())
}
/// create DNS challenge
async fn create_dns_challenge(&self, challenge: &Challenge) -> DBResult<()> {
let now = now_unix_time_stamp();
sqlx::query!(
"INSERT INTO
starchart_dns_challenges (hostname, value, key, created )
VALUES ($1, $2, $3, $4);",
challenge.url,
challenge.value,
challenge.key,
now,
)
.execute(&self.pool)
.await
.map_err(map_register_err)?;
Ok(())
}
/// Get all repositories
async fn get_all_repositories(&self, offset: u32, limit: u32) -> DBResult<Vec<Repository>> {
#[allow(non_snake_case)]

View file

@ -23,12 +23,10 @@ use std::cell::RefCell;
use tera::Context;
use url::Url;
use db_core::prelude::*;
use crate::errors::ServiceResult;
use crate::pages::errors::*;
use crate::settings::Settings;
use crate::verify::TXTChallenge;
use crate::verify::{Challenge, TXTChallenge};
use crate::*;
pub use crate::pages::*;
@ -88,37 +86,28 @@ pub fn services(cfg: &mut web::ServiceConfig) {
pub async fn add_submit(
payload: web::Form<AddChallengePayload>,
ctx: WebCtx,
db: WebDB,
) -> PageResult<impl Responder, AddChallenge> {
async fn _add_submit(
payload: &AddChallengePayload,
ctx: &ArcCtx,
db: &BoxDB,
) -> ServiceResult<TXTChallenge> {
let url_hostname = Url::parse(&payload.hostname).unwrap();
let key = TXTChallenge::get_challenge_txt_key(ctx, &url_hostname);
if db.dns_challenge_exists(&key).await? {
let value = db.get_dns_challenge(&key).await?.value;
Ok(TXTChallenge { key, value })
} else {
let challenge = TXTChallenge::new(ctx, &url_hostname);
let challenge = TXTChallenge::new(ctx, &url_hostname);
let c = Challenge {
key: challenge.key,
value: challenge.value,
url: url_hostname.to_string(),
};
db.create_dns_challenge(&c).await?;
let c = Challenge {
key: challenge.key,
value: challenge.value,
url: url_hostname.to_string(),
};
let challenge = TXTChallenge {
key: c.key,
value: c.value,
};
Ok(challenge)
}
let challenge = TXTChallenge {
key: c.key,
value: c.value,
};
Ok(challenge)
}
let challenge = _add_submit(&payload, &ctx, &db)
let challenge = _add_submit(&payload, &ctx)
.await
.map_err(|e| PageError::new(AddChallenge::new(&ctx.settings, Some(&payload)), e))?;
@ -173,11 +162,6 @@ mod tests {
};
println!("{}", payload.hostname);
let hostname = Url::parse(&payload.hostname).unwrap();
let key = TXTChallenge::get_challenge_txt_key(&ctx, &hostname);
db.delete_dns_challenge(&key).await.unwrap();
assert!(!db.dns_challenge_exists(&key).await.unwrap());
let resp = test::call_service(
&app,
@ -190,10 +174,6 @@ mod tests {
}
assert_eq!(resp.status(), StatusCode::FOUND);
assert!(db.dns_challenge_exists(&key).await.unwrap());
let challenge = db.get_dns_challenge(&key).await.unwrap().value;
// replay config
let resp = test::call_service(
&app,
@ -202,8 +182,5 @@ mod tests {
.await;
assert_eq!(resp.status(), StatusCode::FOUND);
assert!(db.dns_challenge_exists(&key).await.unwrap());
assert_eq!(challenge, db.get_dns_challenge(&key).await.unwrap().value);
}
}

View file

@ -23,12 +23,9 @@ use std::cell::RefCell;
use tera::Context;
use url::Url;
use db_core::prelude::*;
use crate::errors::ServiceResult;
use crate::pages::errors::*;
use crate::settings::Settings;
use crate::verify::TXTChallenge;
use crate::verify::{Challenge, TXTChallenge};
use crate::*;
pub use crate::pages::*;
@ -50,7 +47,7 @@ impl CtxError for VerifyChallenge {
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub struct VerifyChallengePayload {
pub hostname: String,
pub hostname: Url,
}
impl VerifyChallenge {
@ -78,19 +75,14 @@ impl VerifyChallenge {
#[get(path = "PAGES.auth.verify")]
pub async fn get_verify(
ctx: WebCtx,
db: WebDB,
query: web::Query<VerifyChallengePayload>,
) -> PageResult<impl Responder, VerifyChallenge> {
let payload = query.into_inner();
let value = _get_challenge(&payload, &db).await.map_err(|e| {
let challenge = Challenge {
key: payload.hostname,
value: "".into(),
url: "".into(),
};
PageError::new(VerifyChallenge::new(&ctx.settings, &challenge), e)
})?;
let challenge = TXTChallenge::new(&ctx, &query.hostname);
let value = Challenge {
key: challenge.key,
value: challenge.value,
url: query.hostname.to_string(),
};
let login = VerifyChallenge::page(&ctx.settings, &value);
let html = ContentType::html();
@ -102,11 +94,6 @@ pub fn services(cfg: &mut web::ServiceConfig) {
cfg.service(submit_verify);
}
async fn _get_challenge(payload: &VerifyChallengePayload, db: &BoxDB) -> ServiceResult<Challenge> {
let value = db.get_dns_challenge(&payload.hostname).await?;
Ok(value)
}
#[post(path = "PAGES.auth.verify")]
pub async fn submit_verify(
payload: web::Form<VerifyChallengePayload>,
@ -115,31 +102,15 @@ pub async fn submit_verify(
federate: WebFederate,
) -> PageResult<impl Responder, VerifyChallenge> {
let payload = payload.into_inner();
let value = _get_challenge(&payload, &db).await.map_err(|e| {
let challenge = Challenge {
key: payload.hostname.clone(),
value: "".into(),
url: "".into(),
};
PageError::new(VerifyChallenge::new(&ctx.settings, &challenge), e)
})?;
let challenge = TXTChallenge {
key: payload.hostname,
value: value.value,
};
let challenge = TXTChallenge::new(&ctx, &payload.hostname);
match challenge.verify_txt().await {
Ok(true) => {
let _ = db.delete_dns_challenge(&challenge.key).await;
let ctx = ctx.clone();
let federate = federate.clone();
let db = db.clone();
let fut = async move {
ctx.crawl(&Url::parse(&value.url).unwrap(), &db, &federate)
.await;
ctx.crawl(&payload.hostname, &db, &federate).await;
};
tokio::spawn(fut);

View file

@ -22,10 +22,20 @@ use trust_dns_resolver::{
};
use url::Url;
use crate::utils::get_random;
use crate::ArcCtx;
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
/// represents a DNS challenge
pub struct Challenge {
/// url of the forge instance
pub url: String,
/// key of TXT record
pub key: String,
/// value of TXT record
pub value: String,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct TXTChallenge {
pub key: String,
pub value: String,
@ -49,7 +59,7 @@ impl TXTChallenge {
pub fn new(ctx: &ArcCtx, hostname: &Url) -> Self {
let key = Self::get_challenge_txt_key(ctx, hostname);
let value = get_random(VALUES_LEN);
let value = ctx.settings.server.domain.clone();
Self { key, value }
}