Skip to content

Commit

Permalink
Add support for TOTP
Browse files Browse the repository at this point in the history
  • Loading branch information
photino committed Oct 10, 2023
1 parent bc1b0d6 commit ab91000
Show file tree
Hide file tree
Showing 8 changed files with 75 additions and 26 deletions.
2 changes: 1 addition & 1 deletion examples/actix-app/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ features = ["derive"]
[dependencies.zino]
path = "../../zino"
version = "0.13.0"
features = ["actix", "export-pdf"]
features = ["actix"]

[dependencies.zino-core]
path = "../../zino-core"
Expand Down
6 changes: 3 additions & 3 deletions examples/actix-app/src/router/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,7 @@ fn file_router(cfg: &mut ServiceConfig) {
cfg.service(
scope("/file")
.route("/upload", post().to(file::upload))
.route("/decrypt", get().to(file::decrypt))
.wrap(middleware::UserSessionInitializer),
.route("/decrypt", get().to(file::decrypt)),
);
}

Expand All @@ -60,7 +59,8 @@ fn tag_router(cfg: &mut ServiceConfig) {
.route("/{id}/delete", post().to(Tag::delete))
.route("/{id}/update", post().to(Tag::update))
.route("/{id}/view", get().to(Tag::view))
.route("/list", get().to(Tag::list)),
.route("/list", get().to(Tag::list))
.route("/schema", get().to(Tag::schema)),
);
}

Expand Down
2 changes: 1 addition & 1 deletion examples/axum-app/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ features = ["derive"]
[dependencies.zino]
path = "../../zino"
version = "0.13.0"
features = ["axum", "export-pdf"]
features = ["axum"]

[dependencies.zino-core]
path = "../../zino-core"
Expand Down
6 changes: 3 additions & 3 deletions examples/axum-app/src/router/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,7 @@ pub fn routes() -> Vec<Router> {
// File controller.
let router = Router::new()
.route("/file/upload", post(file::upload))
.route("/file/decrypt", get(file::decrypt))
.layer(from_fn(middleware::init_user_session));
.route("/file/decrypt", get(file::decrypt));
routes.push(router);

// User controller.
Expand All @@ -47,7 +46,8 @@ pub fn routes() -> Vec<Router> {
.route("/tag/:id/delete", post(Tag::delete))
.route("/tag/:id/update", post(Tag::update))
.route("/tag/:id/view", get(Tag::view))
.route("/tag/list", get(Tag::list));
.route("/tag/list", get(Tag::list))
.route("/tag/schema", get(Tag::schema));
routes.push(router);

// Task controller.
Expand Down
19 changes: 13 additions & 6 deletions zino-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ accessor-mini-moka = ["accessor", "opendal/services-mini-moka"]
accessor-moka = ["accessor", "opendal/services-moka"]
accessor-onedrive = ["accessor", "opendal/services-onedrive"]
accessor-persy = ["accessor", "opendal/services-persy"]
accessor-redis = ["accessor", "opendal/services-redis"]
accessor-redb = ["accessor", "opendal/services-redb"]
accessor-redis = ["accessor", "opendal/services-redis"]
accessor-sled = ["accessor", "opendal/services-sled"]
accessor-supabase = ["accessor", "opendal/services-supabase"]
accessor-wasabi = ["accessor", "opendal/services-wasabi"]
Expand All @@ -46,12 +46,13 @@ all-accessors = [
"accessor-moka",
"accessor-onedrive",
"accessor-persy",
"accessor-redis",
"accessor-redb",
"accessor-redis",
"accessor-sled",
"accessor-supabase",
"accessor-wasabi",
]
all-chatbots = ["chatbot", "chatbot-openai"]
all-connectors = [
"connector",
"connector-arrow",
Expand All @@ -60,8 +61,8 @@ all-connectors = [
"connector-postgres",
"connector-sqlite",
]
all-chatbots = ["chatbot", "chatbot-openai"]
all-formats = ["format", "format-pdf"]
auth-totp = ["dep:totp-rs"]
cache = ["dep:lru"]
chatbot = []
chatbot-openai = ["dep:async-openai", "chatbot"]
Expand All @@ -71,7 +72,7 @@ connector-http = ["connector"]
connector-mysql = ["connector", "sqlx", "sqlx/mysql"]
connector-postgres = ["connector", "sqlx", "sqlx/postgres"]
connector-sqlite = ["connector", "sqlx", "sqlx/sqlite"]
crypto-sm = ["dep:cfb-mode", "dep:sm3", "dep:sm4"]
crypto-sm = ["dep:ctr", "dep:sm3", "dep:sm4"]
default = ["runtime-tokio", "tls-rustls"]
format = []
format-pdf = ["format", "dep:printpdf"]
Expand All @@ -80,6 +81,7 @@ full = [
"all-chatbots",
"all-connectors",
"all-formats",
"auth-totp",
"cache",
"orm",
"view",
Expand Down Expand Up @@ -147,8 +149,8 @@ features = ["std"]
version = "0.14.3"
optional = true

[dependencies.cfb-mode]
version = "0.8.2"
[dependencies.ctr]
version = "0.9.2"
optional = true

[dependencies.chrono]
Expand Down Expand Up @@ -217,6 +219,11 @@ features = [
version = "1.19.1"
optional = true

[dependencies.totp-rs]
version = "5.4.0"
optional = true
features = ["otpauth", "qr"]

[dependencies.tracing-subscriber]
version = "0.3.17"
features = [
Expand Down
23 changes: 17 additions & 6 deletions zino-core/src/auth/access_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ use hmac::{
use rand::{distributions::Alphanumeric, Rng};
use std::{borrow::Cow, env, fmt, iter, sync::LazyLock};

#[cfg(feature = "auth-totp")]
use totp_rs::{Algorithm, TOTP};

/// Access key ID.
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct AccessKeyId(String);
Expand Down Expand Up @@ -71,7 +74,7 @@ impl<'a> From<Cow<'a, str>> for AccessKeyId {

/// Secrect access key.
#[derive(Debug, Clone)]
pub struct SecretAccessKey(String);
pub struct SecretAccessKey(Vec<u8>);

impl SecretAccessKey {
/// Creates a new instance for the Access key ID.
Expand All @@ -87,20 +90,28 @@ impl SecretAccessKey {
{
let mut mac = H::new_from_slice(key.as_ref()).expect("HMAC can take key of any size");
mac.update(access_key_id.as_ref());
Self(base64::encode(mac.finalize().into_bytes()))
Self(mac.finalize().into_bytes().to_vec())
}

/// Returns a string slice.
/// Returns a byte slice.
#[inline]
pub fn as_str(&self) -> &str {
self.0.as_str()
pub fn as_bytes(&self) -> &[u8] {
self.0.as_slice()
}

/// Consumes `self` and generates a TOTP used for 2FA authentification.
#[cfg(feature = "auth-totp")]
pub fn generate_totp(self, issuer: Option<String>, account_name: String) -> TOTP {
let mut secret = self.0;
secret.truncate(20);
TOTP::new_unchecked(Algorithm::SHA1, 6, 1, 30, secret, issuer, account_name)
}
}

impl fmt::Display for SecretAccessKey {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0)
write!(f, "{}", base64::encode(self.as_bytes()))
}
}

Expand Down
12 changes: 7 additions & 5 deletions zino-core/src/crypto/sm4.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use crate::error::Error;
use cfb_mode::{Decryptor, Encryptor};
use ctr::Ctr64LE;
use rand::Rng;
use sm4::{
cipher::{generic_array::GenericArray, AsyncStreamCipher, KeyIvInit},
cipher::{KeyIvInit, StreamCipher},
Sm4,
};

Expand All @@ -20,7 +20,8 @@ pub(crate) fn encrypt(plaintext: &[u8], key: &[u8]) -> Result<Vec<u8>, Error> {

let mut buf = plaintext.to_vec();
let key = padded_key(key).into();
Encryptor::<Sm4>::new(&key, &nonce.into()).encrypt(&mut buf);
let iv = nonce.into();
Ctr64LE::<Sm4>::new(&key, &iv).apply_keystream(&mut buf);
buf.extend_from_slice(&nonce);
Ok(buf)
}
Expand All @@ -32,11 +33,12 @@ pub(crate) fn decrypt(data: &[u8], key: &[u8]) -> Result<Vec<u8>, Error> {
}

let (ciphertext, bytes) = data.split_at(data.len() - NONCE_SIZE);
let nonce = GenericArray::from_slice(bytes);
let nonce: [u8; NONCE_SIZE] = bytes.try_into()?;

let mut buf = ciphertext.to_vec();
let key = padded_key(key).into();
Decryptor::<Sm4>::new(&key, &nonce).decrypt(&mut buf);
let iv = nonce.into();
Ctr64LE::<Sm4>::new(&key, &iv).apply_keystream(&mut buf);
Ok(buf)
}

Expand Down
31 changes: 30 additions & 1 deletion zino/src/controller/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,16 @@ pub trait DefaultController<K, U = K> {

/// Exports model data.
async fn export(req: Self::Request) -> Self::Result;

/// Gets the model schema.
async fn schema(req: Self::Request) -> Self::Result;
}

#[cfg(any(feature = "actix", feature = "axum"))]
#[cfg(feature = "orm")]
use zino_core::{
database::{ModelAccessor, ModelHelper},
extension::{JsonObjectExt, JsonValueExt},
extension::JsonObjectExt,
model::{ModelHooks, Query},
request::RequestContext,
response::{ExtractRejection, Rejection, StatusCode},
Expand Down Expand Up @@ -285,6 +288,8 @@ where
"msgpack" => res.set_msgpack_response(models),
#[cfg(feature = "export-pdf")]
"pdf" => {
use zino_core::extension::JsonValueExt;

res.set_json_data(models);
res.set_content_type("application/pdf");
res.set_data_transformer(|data| {
Expand All @@ -302,4 +307,28 @@ where
}
Ok(res.into())
}

async fn schema(req: Self::Request) -> Self::Result {
let reader_available = Self::acquire_reader()
.await
.is_ok_and(|cp| cp.is_available());
let writer_available = Self::acquire_writer()
.await
.is_ok_and(|cp| cp.is_available());
let data = serde_json::json!({
"model_name": Self::model_name(),
"model_namespace": Self::model_namespace(),
"table_name": Self::table_name(),
"columns": Self::columns(),
"readonly_fields": Self::readonly_fields(),
"writeonly_fields": Self::writeonly_fields(),
"primary_key_name": Self::PRIMARY_KEY_NAME,
"reader_available": reader_available,
"writer_available": writer_available,
});

let mut res = crate::Response::default().context(&req);
res.set_json_data(data);
Ok(res.into())
}
}

0 comments on commit ab91000

Please sign in to comment.