cloudflare-ddns-service/src/network.rs
Jan Christian Grünhage ec8b893f6a feat: migrate to api tokens
Cloudflare's v4 API provides three ways of authenticating,
with the API tokens being the new, standard conform, recommended
way. This patch removes the use of the old auth_key + email combo
and replaces it with API tokens.

BREAKING CHANGE: auth keys can't be used anymore, switch to api tokens
2020-04-20 23:04:31 +02:00

172 lines
4.1 KiB
Rust

use serde_derive::{Deserialize, Serialize};
use anyhow::{anyhow};
#[derive(Deserialize, Debug)]
struct CloudflareListResponse {
success: bool,
errors: Vec<String>,
result: Option<Vec<ObjectWithId>>,
}
#[derive(Deserialize, Debug)]
struct CloudflareUpdateResponse {
success: bool,
errors: Vec<String>,
result: ObjectWithId,
}
#[derive(Deserialize, Debug, PartialEq)]
struct ObjectWithId {
id: String,
}
#[derive(Serialize, Debug)]
struct UpdateIpData {
id: String,
r#type: String,
name: String,
content: String,
}
pub fn get_zone_identifier(zone: &str, api_token: &str) -> anyhow::Result<String> {
let client = reqwest::blocking::Client::new();
let url = format!("https://api.cloudflare.com/client/v4/zones?name={}", zone);
let response = client
.get(&url)
.header("Authorization", format!("Bearer {}", api_token))
.header("Content-Type", "application/json")
.send()?;
if response.status() != 200 {
return Err(anyhow!("API Error: HTTP {}", response.status()));
}
let body: CloudflareListResponse = response
.json()?;
if !body.success {
let err: String = body
.errors
.iter()
.map(|s| format!("{}\n", s.to_owned()))
.collect();
return Err(anyhow!("API Error: {}", err));
}
let zones = match body.result {
Some(v) => v,
None => {
return Err(anyhow!("No zones returned"))
},
};
Ok(zones[0].id.clone())
}
pub fn get_dns_record_id(
zone_id: &str,
domain: &str,
api_token: &str,
) -> anyhow::Result<String> {
let client = reqwest::blocking::Client::new();
let url = format!(
"https://api.cloudflare.com/client/v4/zones/{}/dns_records?name={}",
zone_id, domain
);
let response = client
.get(&url)
.header("Authorization", format!("Bearer {}", api_token))
.header("Content-Type", "application/json")
.send()?;
if response.status() != 200 {
return Err(anyhow!("API Error: HTTP {}", response.status()));
}
let body: CloudflareListResponse = response.json()?;
if !body.success {
let err: String = body
.errors
.iter()
.map(|s| format!("{}\n", s.to_owned()))
.collect();
return Err(anyhow!("API Error: {}", err));
}
let records = match body.result {
Some(v) => v,
None => {
return Err(anyhow!("No DNS records returned"))
},
};
let id = match records.first() {
Some(v) => v.id.clone(),
None => {
return Err(anyhow!(
"Unexpected API result for DNS record. Check if you passed the right options."
))
}
};
Ok(id)
}
pub fn get_current_ip() -> anyhow::Result<String> {
Ok(reqwest::blocking::Client::new()
.get("http://ipv4.icanhazip.com")
.send()?
.text()?
.trim()
.into())
}
pub fn update_ddns(
ip: &str,
domain: &str,
zone_id: &str,
record_id: &str,
api_token: &str,
) -> anyhow::Result<()> {
let client = reqwest::blocking::Client::new();
let url = format!(
"https://api.cloudflare.com/client/v4/zones/{}/dns_records/{}",
zone_id, record_id
);
let update_data = UpdateIpData {
id: zone_id.to_owned(),
r#type: "A".to_owned(),
name: domain.to_owned(),
content: ip.to_owned(),
};
let response = client
.put(&url)
.header("Authorization", format!("Bearer {}", api_token))
.header("Content-Type", "application/json")
.json(&update_data)
.send()?;
if response.status() != 200 {
return Err(anyhow!("API Error: HTTP {}", response.status()));
}
let body: CloudflareUpdateResponse = response
.json()?;
if !body.success {
let err: String = body
.errors
.iter()
.map(|s| format!("{}\n", s.to_owned()))
.collect();
return Err(anyhow!("Unsuccessful update of DNS record: {}", err));
}
Ok(())
}