starchart/src/verify.rs
Aravinth Manivannan e726f2234d
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
2023-02-22 12:05:49 +05:30

107 lines
3.4 KiB
Rust

/*
* ForgeFlux StarChart - A federated software forge spider
* Copyright © 2022 Aravinth Manivannan <realaravinth@batsense.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
use serde::{Deserialize, Serialize};
use trust_dns_resolver::{
config::{ResolverConfig, ResolverOpts},
AsyncResolver,
};
use url::Url;
use crate::ArcCtx;
#[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,
}
const VALUES_LEN: usize = 30;
impl TXTChallenge {
pub fn get_challenge_txt_key_prefix(ctx: &ArcCtx) -> String {
// starchart-{{ starchart instance's hostname}}.{{ forge instance's hostname }}
format!("starchart-{}", &ctx.settings.server.domain)
}
pub fn get_challenge_txt_key(ctx: &ArcCtx, hostname: &Url) -> String {
format!(
"{}.{}",
Self::get_challenge_txt_key_prefix(ctx),
hostname.host_str().unwrap()
)
}
pub fn new(ctx: &ArcCtx, hostname: &Url) -> Self {
let key = Self::get_challenge_txt_key(ctx, hostname);
let value = ctx.settings.server.domain.clone();
Self { key, value }
}
pub async fn verify_txt(&self) -> Result<bool, Box<dyn std::error::Error>> {
let conf = ResolverConfig::cloudflare_tls();
let opts = ResolverOpts::default();
let resolver = AsyncResolver::tokio(conf, opts)?;
let res = resolver.txt_lookup(&self.key).await?;
Ok(res.iter().any(|r| r.to_string() == self.value))
}
}
#[cfg(test)]
pub mod tests {
use super::*;
use crate::tests::sqlx_sqlite;
pub const BASE_DOMAIN: &str = "https://forge.forgeflux.org";
pub const VALUE: &str = "ifthisvalueisretrievedbyforgefluxstarchartthenthetestshouldpass";
#[actix_rt::test]
async fn verify_txt_works() {
// please note that this DNS record is in prod
let (_db, ctx, _federate, _tmp_dir) = sqlx_sqlite::get_ctx().await;
let base_hostname = Url::parse(BASE_DOMAIN).unwrap();
let key = TXTChallenge::get_challenge_txt_key(&ctx, &base_hostname);
let mut txt_challenge = TXTChallenge {
value: VALUE.to_string(),
key: key.clone(),
};
assert_eq!(
TXTChallenge::get_challenge_txt_key(&ctx, &base_hostname),
key,
);
assert!(
txt_challenge.verify_txt().await.unwrap(),
"TXT Challenge verification test"
);
txt_challenge.value = key;
assert!(!txt_challenge.verify_txt().await.unwrap());
}
}