delete old files

This commit is contained in:
Hannaeko 2022-04-24 10:50:46 +02:00
parent 54b18a8585
commit b70195bfe1
2 changed files with 0 additions and 894 deletions

View file

@ -1,480 +0,0 @@
use std::{convert::{TryFrom, TryInto}, future::Future, net::{Ipv6Addr, Ipv4Addr}, pin::Pin, task::{Context, Poll}};
use std::fmt;
use std::ops::{Deref, DerefMut};
use rocket::{Request, State, http::Status, request::{FromParam, FromRequest, Outcome}};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use tokio::{net::TcpStream as TokioTcpStream, task};
use trust_dns_client::{client::AsyncClient, error::ClientError, op::DnsResponse, serialize::binary::BinEncoder, tcp::TcpClientStream};
use trust_dns_proto::error::{ProtoError};
use trust_dns_proto::iocompat::AsyncIoTokioAsStd;
use super::trust_dns_types::{self, Name};
use crate::config::Config;
#[derive(Deserialize, Serialize, Clone)]
#[serde(tag = "Type")]
#[serde(rename_all = "UPPERCASE")]
pub enum RData {
#[serde(rename_all = "PascalCase")]
A {
address: Ipv4Addr
},
#[serde(rename_all = "PascalCase")]
AAAA {
address: Ipv6Addr
},
#[serde(rename_all = "PascalCase")]
CAA {
issuer_critical: bool,
value: String,
property_tag: String,
},
#[serde(rename_all = "PascalCase")]
CNAME {
target: SerdeName
},
// HINFO(HINFO),
// HTTPS(SVCB),
#[serde(rename_all = "PascalCase")]
MX {
preference: u16,
mail_exchanger: SerdeName
},
// NAPTR(NAPTR),
#[serde(rename_all = "PascalCase")]
NULL {
data: String
},
#[serde(rename_all = "PascalCase")]
NS {
target: SerdeName
},
// OPENPGPKEY(OPENPGPKEY),
// OPT(OPT),
#[serde(rename_all = "PascalCase")]
PTR {
target: SerdeName
},
#[serde(rename_all = "PascalCase")]
SOA {
master_server_name: SerdeName,
maintainer_name: SerdeName,
refresh: i32,
retry: i32,
expire: i32,
minimum: u32,
serial: u32
},
#[serde(rename_all = "PascalCase")]
SRV {
server: SerdeName,
port: u16,
priority: u16,
weight: u16,
},
#[serde(rename_all = "PascalCase")]
SSHFP {
algorithm: u8,
digest_type: u8,
fingerprint: String,
},
// SVCB(SVCB),
// TLSA(TLSA),
#[serde(rename_all = "PascalCase")]
TXT {
text: String
},
// TODO: Eventually allow deserialization of DNSSEC records
#[serde(skip)]
DNSSEC(trust_dns_types::DNSSECRData),
#[serde(rename_all = "PascalCase")]
Unknown {
code: u16,
data: String,
},
// ZERO,
// TODO: DS
}
impl From<trust_dns_types::RData> for RData {
fn from(rdata: trust_dns_types::RData) -> RData {
match rdata {
trust_dns_types::RData::A(address) => RData::A { address },
trust_dns_types::RData::AAAA(address) => RData::AAAA { address },
// Still a draft, no iana number yet, I don't to put something that is not currently supported so that's why NULL and not unknown.
// TODO: probably need better error here, I don't know what to do about that as this would require to change the From for something else.
// (empty data because I'm lazy)
trust_dns_types::RData::ANAME(_) => RData::NULL {
data: String::new()
},
trust_dns_types::RData::CNAME(target) => RData::CNAME {
target: SerdeName(target)
},
trust_dns_types::RData::CAA(caa) => RData::CAA {
issuer_critical: caa.issuer_critical(),
value: format!("{}", CAAValue(caa.value())),
property_tag: caa.tag().as_str().to_string(),
},
trust_dns_types::RData::MX(mx) => RData::MX {
preference: mx.preference(),
mail_exchanger: SerdeName(mx.exchange().clone())
},
trust_dns_types::RData::NULL(null) => RData::NULL {
data: base64::encode(null.anything().map(|data| data.to_vec()).unwrap_or_default())
},
trust_dns_types::RData::NS(target) => RData::NS {
target: SerdeName(target)
},
trust_dns_types::RData::PTR(target) => RData::PTR {
target: SerdeName(target)
},
trust_dns_types::RData::SOA(soa) => RData::SOA {
master_server_name: SerdeName(soa.mname().clone()),
maintainer_name: SerdeName(soa.rname().clone()),
refresh: soa.refresh(),
retry: soa.retry(),
expire: soa.expire(),
minimum: soa.minimum(),
serial: soa.serial()
},
trust_dns_types::RData::SRV(srv) => RData::SRV {
server: SerdeName(srv.target().clone()),
port: srv.port(),
priority: srv.priority(),
weight: srv.weight(),
},
trust_dns_types::RData::SSHFP(sshfp) => RData::SSHFP {
algorithm: sshfp.algorithm().into(),
digest_type: sshfp.fingerprint_type().into(),
fingerprint: trust_dns_types::sshfp::HEX.encode(sshfp.fingerprint()),
},
//TODO: This might alter data if not utf8 compatible, probably need to be replaced
//TODO: check whether concatenating txt data is harmful or not
trust_dns_types::RData::TXT(txt) => RData::TXT { text: format!("{}", txt) },
trust_dns_types::RData::DNSSEC(data) => RData::DNSSEC(data),
rdata => {
let code = rdata.to_record_type().into();
let mut data = Vec::new();
let mut encoder = BinEncoder::new(&mut data);
// TODO: need better error handling (use TryFrom ?)
rdata.emit(&mut encoder).expect("could not encode data");
RData::Unknown {
code,
data: base64::encode(data),
}
}
}
}
}
impl TryFrom<RData> for trust_dns_types::RData {
type Error = ProtoError;
fn try_from(rdata: RData) -> Result<Self, Self::Error> {
Ok(match rdata {
RData::A { address } => trust_dns_types::RData::A(address),
RData::AAAA { address } => trust_dns_types::RData::AAAA(address),
// TODO: Round trip test all types below (currently not tested...)
RData::CAA { issuer_critical, value, property_tag } => {
let property = trust_dns_types::caa::Property::from(property_tag);
let caa_value = {
// TODO: duplicate of trust_dns_client::serialize::txt::rdata_parser::caa::parse
// because caa::read_value is private
match property {
trust_dns_types::caa::Property::Issue | trust_dns_types::caa::Property::IssueWild => {
let value = trust_dns_types::caa::read_issuer(value.as_bytes())?;
trust_dns_types::caa::Value::Issuer(value.0, value.1)
}
trust_dns_types::caa::Property::Iodef => {
let url = trust_dns_types::caa::read_iodef(value.as_bytes())?;
trust_dns_types::caa::Value::Url(url)
}
trust_dns_types::caa::Property::Unknown(_) => trust_dns_types::caa::Value::Unknown(value.as_bytes().to_vec()),
}
};
trust_dns_types::RData::CAA(trust_dns_types::caa::CAA {
issuer_critical,
tag: property,
value: caa_value,
})
},
RData::CNAME { target } => trust_dns_types::RData::CNAME(target.into_inner()),
RData::MX { preference, mail_exchanger } => trust_dns_types::RData::MX(
trust_dns_types::mx::MX::new(preference, mail_exchanger.into_inner())
),
RData::NULL { data } => trust_dns_types::RData::NULL(
trust_dns_types::null::NULL::with(
base64::decode(data).map_err(|e| ProtoError::from(format!("{}", e)))?
)
),
RData::NS { target } => trust_dns_types::RData::NS(target.into_inner()),
RData::PTR { target } => trust_dns_types::RData::PTR(target.into_inner()),
RData::SOA {
master_server_name,
maintainer_name,
refresh,
retry,
expire,
minimum,
serial
} => trust_dns_types::RData::SOA(
trust_dns_types::soa::SOA::new(
master_server_name.into_inner(),
maintainer_name.into_inner(),
serial,
refresh,
retry,
expire,
minimum,
)
),
RData::SRV { server, port, priority, weight } => trust_dns_types::RData::SRV(
trust_dns_types::srv::SRV::new(priority, weight, port, server.into_inner())
),
RData::SSHFP { algorithm, digest_type, fingerprint } => trust_dns_types::RData::SSHFP(
trust_dns_types::sshfp::SSHFP::new(
// NOTE: This allows unassigned algorithms
trust_dns_types::sshfp::Algorithm::from(algorithm),
trust_dns_types::sshfp::FingerprintType::from(digest_type),
trust_dns_types::sshfp::HEX.decode(fingerprint.as_bytes()).map_err(|e| ProtoError::from(format!("{}", e)))?
)
),
RData::TXT { text } => trust_dns_types::RData::TXT(trust_dns_types::txt::TXT::new(vec![text])),
// TODO: Error out for DNSSEC? Prefer downstream checks?
RData::DNSSEC(_) => todo!(),
// TODO: Disallow unknown? (could be used to bypass unsopported types?) Prefer downstream checks?
RData::Unknown { code, data } => todo!(),
})
}
}
struct CAAValue<'a>(&'a trust_dns_types::caa::Value);
// trust_dns Display implementation panics if no parameters
// Implementation based on caa::emit_value
// Also the quotes are strips to render in JSON
impl<'a> fmt::Display for CAAValue<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
match self.0 {
trust_dns_types::caa::Value::Issuer(name, parameters) => {
if let Some(name) = name {
write!(f, "{}", name)?;
}
if name.is_none() && parameters.is_empty() {
write!(f, ";")?;
}
for value in parameters {
write!(f, "; {}", value)?;
}
}
trust_dns_types::caa::Value::Url(url) => write!(f, "{}", url)?,
trust_dns_types::caa::Value::Unknown(v) => write!(f, "{:?}", v)?,
}
Ok(())
}
}
#[derive(Deserialize, Serialize, Clone)]
pub enum DNSClass {
IN,
CH,
HS,
NONE,
ANY,
OPT(u16),
}
impl From<trust_dns_types::DNSClass> for DNSClass {
fn from(dns_class: trust_dns_types::DNSClass) -> DNSClass {
match dns_class {
trust_dns_types::DNSClass::IN => DNSClass::IN,
trust_dns_types::DNSClass::CH => DNSClass::CH,
trust_dns_types::DNSClass::HS => DNSClass::HS,
trust_dns_types::DNSClass::NONE => DNSClass::NONE,
trust_dns_types::DNSClass::ANY => DNSClass::ANY,
trust_dns_types::DNSClass::OPT(v) => DNSClass::OPT(v),
}
}
}
impl From<DNSClass> for trust_dns_types::DNSClass {
fn from(dns_class: DNSClass) -> trust_dns_types::DNSClass {
match dns_class {
DNSClass::IN => trust_dns_types::DNSClass::IN,
DNSClass::CH => trust_dns_types::DNSClass::CH,
DNSClass::HS => trust_dns_types::DNSClass::HS,
DNSClass::NONE => trust_dns_types::DNSClass::NONE,
DNSClass::ANY => trust_dns_types::DNSClass::ANY,
DNSClass::OPT(v) => trust_dns_types::DNSClass::OPT(v),
}
}
}
// Reimplement this type here as ClientReponse in trust-dns crate have private fields
pub struct ClientResponse<R>(pub(crate) R)
where
R: Future<Output = Result<DnsResponse, ProtoError>> + Send + Unpin + 'static;
impl<R> Future for ClientResponse<R>
where
R: Future<Output = Result<DnsResponse, ProtoError>> + Send + Unpin + 'static,
{
type Output = Result<DnsResponse, ClientError>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
// This is from the future_utils crate, we simply reuse the reexport from Rocket
rocket::futures::FutureExt::poll_unpin(&mut self.0, cx).map_err(ClientError::from)
}
}
#[derive(Deserialize, Serialize, Clone)]
pub struct Record {
#[serde(rename = "Name")]
pub name: SerdeName,
// TODO: Make class optional, default to IN
#[serde(rename = "Class")]
pub dns_class: DNSClass,
#[serde(rename = "TTL")]
pub ttl: u32,
#[serde(flatten)]
pub rdata: RData,
}
impl From<trust_dns_types::Record> for Record {
fn from(record: trust_dns_types::Record) -> Record {
Record {
name: SerdeName(record.name().clone()),
dns_class: record.dns_class().into(),
ttl: record.ttl(),
rdata: record.into_data().into(),
}
}
}
impl TryFrom<Record> for trust_dns_types::Record {
type Error = ProtoError;
fn try_from(record: Record) -> Result<Self, Self::Error> {
let mut trust_dns_record = trust_dns_types::Record::from_rdata(record.name.into_inner(), record.ttl, record.rdata.try_into()?);
trust_dns_record.set_dns_class(record.dns_class.into());
Ok(trust_dns_record)
}
}
#[derive(Debug, Clone)]
pub struct SerdeName(Name);
impl<'de> Deserialize<'de> for SerdeName {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>
{
use serde::de::Error;
String::deserialize(deserializer)
.and_then(|string|
Name::from_utf8(&string)
.map_err(|e| Error::custom(e.to_string()))
).map( SerdeName)
}
}
impl Deref for SerdeName {
type Target = Name;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl Serialize for SerdeName {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer
{
self.0.to_utf8().serialize(serializer)
}
}
impl SerdeName {
fn into_inner(self) -> Name {
self.0
}
}
#[derive(Debug, Deserialize)]
pub struct AbsoluteName(SerdeName);
impl<'r> FromParam<'r> for AbsoluteName {
type Error = ProtoError;
fn from_param(param: &'r str) -> Result<Self, Self::Error> {
let mut name = Name::from_utf8(&param)?;
if !name.is_fqdn() {
name.set_fqdn(true);
}
Ok(AbsoluteName(SerdeName(name)))
}
}
impl Deref for AbsoluteName {
type Target = Name;
fn deref(&self) -> &Self::Target {
&self.0.0
}
}
pub struct DnsClient(AsyncClient);
impl Deref for DnsClient {
type Target = AsyncClient;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for DnsClient {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
#[rocket::async_trait]
impl<'r> FromRequest<'r> for DnsClient {
type Error = ();
async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
let config = try_outcome!(request.guard::<State<Config>>().await);
let (stream, handle) = TcpClientStream::<AsyncIoTokioAsStd<TokioTcpStream>>::new(config.dns.server);
let client = AsyncClient::with_timeout(
stream,
handle,
std::time::Duration::from_secs(5),
None);
let (client, bg) = match client.await {
Err(e) => {
println!("Failed to connect to DNS server {:#?}", e);
return Outcome::Failure((Status::InternalServerError, ()))
},
Ok(c) => c
};
task::spawn(bg);
Outcome::Success(DnsClient(client))
}
}

View file

@ -1,414 +0,0 @@
use uuid::Uuid;
use diesel::prelude::*;
use diesel::result::Error as DieselError;
use diesel_derive_enum::DbEnum;
use rocket::{State, request::{FromRequest, Request, Outcome}};
use serde::{Serialize, Deserialize};
use chrono::serde::ts_seconds;
use chrono::prelude::{DateTime, Utc};
use chrono::Duration;
// TODO: Maybe just use argon2 crate directly
use djangohashers::{make_password_with_algorithm, check_password, HasherError, Algorithm};
use jsonwebtoken::{
encode, decode,
Header, Validation,
Algorithm as JwtAlgorithm, EncodingKey, DecodingKey,
errors::Result as JwtResult,
errors::ErrorKind as JwtErrorKind
};
use crate::schema::*;
use crate::DbConn;
use crate::config::Config;
use crate::models::errors::{ErrorResponse, make_500};
use crate::models::dns::AbsoluteName;
const BEARER: &str = "Bearer ";
const AUTH_HEADER: &str = "Authorization";
#[derive(Debug, DbEnum, Deserialize, Clone)]
#[serde(rename_all = "lowercase")]
pub enum Role {
#[db_rename = "admin"]
Admin,
#[db_rename = "zoneadmin"]
ZoneAdmin,
}
// TODO: Store Uuid instead of string??
#[derive(Debug, Queryable, Identifiable, Insertable)]
#[table_name = "user"]
pub struct User {
pub id: String,
}
#[derive(Debug, Queryable, Identifiable, Insertable)]
#[table_name = "localuser"]
#[primary_key(user_id)]
pub struct LocalUser {
pub user_id: String,
pub username: String,
pub password: String,
pub role: Role,
}
#[derive(Debug, Queryable, Identifiable, Insertable)]
#[table_name = "user_zone"]
#[primary_key(user_id, zone_id)]
pub struct UserZone {
pub user_id: String,
pub zone_id: String,
}
#[derive(Debug, Serialize, Queryable, Identifiable, Insertable)]
#[table_name = "zone"]
pub struct Zone {
#[serde(skip)]
pub id: String,
pub name: String,
}
#[derive(Debug, Deserialize)]
pub struct CreateUserRequest {
pub username: String,
pub password: String,
pub email: String,
pub role: Option<Role>
}
#[derive(Debug, Deserialize)]
pub struct AddZoneMemberRequest {
pub id: String,
}
#[derive(Debug, Deserialize)]
pub struct CreateZoneRequest {
pub name: AbsoluteName,
}
// pub struct LdapUserAssociation {
// user_id: Uuid,
// ldap_id: String
// }
#[derive(Debug, Serialize, Deserialize)]
pub struct AuthClaims {
pub jti: String,
pub sub: String,
#[serde(with = "ts_seconds")]
pub exp: DateTime<Utc>,
#[serde(with = "ts_seconds")]
pub iat: DateTime<Utc>,
}
#[derive(Debug, Serialize)]
pub struct AuthTokenResponse {
pub token: String
}
#[derive(Debug, Deserialize)]
pub struct AuthTokenRequest {
pub username: String,
pub password: String,
}
#[derive(Debug)]
pub struct UserInfo {
pub id: String,
pub role: Role,
pub username: String,
}
impl UserInfo {
pub fn is_admin(&self) -> bool {
matches!(self.role, Role::Admin)
}
pub fn check_admin(&self) -> Result<(), UserError> {
if self.is_admin() {
Ok(())
} else {
Err(UserError::PermissionDenied)
}
}
pub fn get_zone(&self, conn: &diesel::SqliteConnection, zone_name: &str) -> Result<Zone, UserError> {
use crate::schema::user_zone::dsl::*;
use crate::schema::zone::dsl::*;
let (res_zone, _): (Zone, UserZone) = zone.inner_join(user_zone)
.filter(name.eq(zone_name))
.filter(user_id.eq(&self.id))
.get_result(conn)
.map_err(|e| match e {
DieselError::NotFound => UserError::ZoneNotFound,
other => UserError::DbError(other)
})?;
Ok(res_zone)
}
pub fn get_zones(&self, conn: &diesel::SqliteConnection) -> Result<Vec<Zone>, UserError> {
use crate::schema::user_zone::dsl::*;
use crate::schema::zone::dsl::*;
let res: Vec<(Zone, UserZone)> = zone.inner_join(user_zone)
.filter(user_id.eq(&self.id))
.get_results(conn)
.map_err(UserError::DbError)?;
Ok(res.into_iter().map(|(z, _)| z).collect())
}
}
#[rocket::async_trait]
impl<'r> FromRequest<'r> for UserInfo {
type Error = ErrorResponse;
async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
let auth_header = match request.headers().get_one(AUTH_HEADER) {
None => return Outcome::Forward(()),
Some(auth_header) => auth_header,
};
let token = if auth_header.starts_with(BEARER) {
auth_header.trim_start_matches(BEARER)
} else {
return ErrorResponse::from(UserError::MalformedHeader).into()
};
let config = try_outcome!(request.guard::<State<Config>>().await.map_failure(make_500));
let conn = try_outcome!(request.guard::<DbConn>().await.map_failure(make_500));
let token_data = AuthClaims::decode(
token, &config.web_app.secret
).map_err(|e| match e.into_kind() {
JwtErrorKind::ExpiredSignature => UserError::ExpiredToken,
_ => UserError::BadToken,
});
let token_data = match token_data {
Err(e) => return ErrorResponse::from(e).into(),
Ok(data) => data
};
let user_id = token_data.sub;
conn.run(move |c| {
match LocalUser::get_user_by_uuid(c, &user_id) {
Err(UserError::NotFound) => ErrorResponse::from(UserError::NotFound).into(),
Err(e) => ErrorResponse::from(e).into(),
Ok(d) => Outcome::Success(d),
}
}).await
}
}
#[derive(Debug)]
pub enum UserError {
ZoneNotFound,
NotFound,
UserConflict,
BadCreds,
BadToken,
ExpiredToken,
MalformedHeader,
PermissionDenied,
DbError(DieselError),
PasswordError(HasherError),
}
impl From<HasherError> for UserError {
fn from(e: HasherError) -> Self {
UserError::PasswordError(e)
}
}
impl From<DieselError> for UserError {
fn from(e: DieselError) -> Self {
UserError::DbError(e)
}
}
impl LocalUser {
pub fn create_user(conn: &diesel::SqliteConnection, user_request: CreateUserRequest) -> Result<UserInfo, UserError> {
use crate::schema::localuser::dsl::*;
use crate::schema::user::dsl::*;
let new_user_id = Uuid::new_v4().to_simple().to_string();
let new_user = User {
id: new_user_id.clone(),
};
let new_localuser = LocalUser {
user_id: new_user_id,
username: user_request.username.clone(),
password: make_password_with_algorithm(&user_request.password, Algorithm::Argon2),
// TODO: Use role from request
role: Role::ZoneAdmin,
};
let res = UserInfo {
id: new_user.id.clone(),
role: new_localuser.role.clone(),
username: new_localuser.username.clone(),
};
conn.immediate_transaction(|| -> diesel::QueryResult<()> {
diesel::insert_into(user)
.values(new_user)
.execute(conn)?;
diesel::insert_into(localuser)
.values(new_localuser)
.execute(conn)?;
Ok(())
}).map_err(|e| match e {
DieselError::DatabaseError(diesel::result::DatabaseErrorKind::UniqueViolation, _) => UserError::UserConflict,
other => UserError::DbError(other)
})?;
Ok(res)
}
pub fn get_user_by_creds(
conn: &diesel::SqliteConnection,
request_username: &str,
request_password: &str
) -> Result<UserInfo, UserError> {
use crate::schema::localuser::dsl::*;
use crate::schema::user::dsl::*;
let (client_user, client_localuser): (User, LocalUser) = user.inner_join(localuser)
.filter(username.eq(request_username))
.get_result(conn)
.map_err(|e| match e {
DieselError::NotFound => UserError::BadCreds,
other => UserError::DbError(other)
})?;
if !check_password(&request_password, &client_localuser.password)? {
return Err(UserError::BadCreds);
}
Ok(UserInfo {
id: client_user.id,
role: client_localuser.role,
username: client_localuser.username,
})
}
pub fn get_user_by_uuid(conn: &diesel::SqliteConnection, request_user_id: &str) -> Result<UserInfo, UserError> {
use crate::schema::localuser::dsl::*;
use crate::schema::user::dsl::*;
let (client_user, client_localuser): (User, LocalUser) = user.inner_join(localuser)
.filter(id.eq(request_user_id))
.get_result(conn)
.map_err(|e| match e {
DieselError::NotFound => UserError::NotFound,
other => UserError::DbError(other)
})?;
Ok(UserInfo {
id: client_user.id,
role: client_localuser.role,
username: client_localuser.username,
})
}
}
impl AuthClaims {
pub fn new(user_info: &UserInfo, token_duration: Duration) -> AuthClaims {
let jti = Uuid::new_v4().to_simple().to_string();
let iat = Utc::now();
let exp = iat + token_duration;
AuthClaims {
jti,
sub: user_info.id.clone(),
exp,
iat,
}
}
pub fn decode(token: &str, secret: &str) -> JwtResult<AuthClaims> {
decode::<AuthClaims>(
token,
&DecodingKey::from_secret(secret.as_ref()),
&Validation::new(JwtAlgorithm::HS256)
).map(|data| data.claims)
}
pub fn encode(self, secret: &str) -> JwtResult<String> {
encode(&Header::default(), &self, &EncodingKey::from_secret(secret.as_ref()))
}
}
// NOTE: Should probably not be implemented here
// also, "UserError" seems like a misleading name
impl Zone {
pub fn get_all(conn: &diesel::SqliteConnection) -> Result<Vec<Zone>, UserError> {
use crate::schema::zone::dsl::*;
zone.get_results(conn)
.map_err(UserError::DbError)
}
pub fn get_by_name(conn: &diesel::SqliteConnection, zone_name: &str) -> Result<Zone, UserError> {
use crate::schema::zone::dsl::*;
zone.filter(name.eq(zone_name))
.get_result(conn)
.map_err(|e| match e {
DieselError::NotFound => UserError::ZoneNotFound,
other => UserError::DbError(other)
})
}
pub fn create_zone(conn: &diesel::SqliteConnection, zone_request: CreateZoneRequest) -> Result<Zone, UserError> {
use crate::schema::zone::dsl::*;
let new_zone = Zone {
id: Uuid::new_v4().to_simple().to_string(),
name: zone_request.name.to_utf8(),
};
diesel::insert_into(zone)
.values(&new_zone)
.execute(conn)
.map_err(|e| match e {
DieselError::DatabaseError(diesel::result::DatabaseErrorKind::UniqueViolation, _) => UserError::UserConflict,
other => UserError::DbError(other)
})?;
Ok(new_zone)
}
pub fn add_member(&self, conn: &diesel::SqliteConnection, new_member: &UserInfo) -> Result<(), UserError> {
use crate::schema::user_zone::dsl::*;
let new_user_zone = UserZone {
zone_id: self.id.clone(),
user_id: new_member.id.clone()
};
let res = diesel::insert_into(user_zone)
.values(new_user_zone)
.execute(conn);
match res {
// If user has already access to the zone, safely ignore the conflit
// TODO: use 'on conflict do nothing' in postgres when we get there
Err(DieselError::DatabaseError(diesel::result::DatabaseErrorKind::UniqueViolation, _)) => (),
Err(e) => return Err(e.into()),
Ok(_) => ()
};
Ok(())
}
}