Skip to content

Commit

Permalink
hash password with bcrypt
Browse files Browse the repository at this point in the history
  • Loading branch information
shenghui0779 committed Nov 5, 2024
1 parent e7ec635 commit 9d8ba14
Show file tree
Hide file tree
Showing 14 changed files with 403 additions and 106 deletions.
402 changes: 345 additions & 57 deletions Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ sha1 = "0.10"
sha2 = "0.10"
hmac = "0.12"
base64 = "0.22"
bcrypt = "0.15"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
config = "0.14"
Expand Down
8 changes: 3 additions & 5 deletions demo_rs.sql
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,8 @@ DROP TABLE IF EXISTS `account`;
CREATE TABLE `account` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '自增ID',
`username` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL DEFAULT '' COMMENT '用户名称',
`password` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL DEFAULT '' COMMENT '用户密码',
`salt` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL DEFAULT '' COMMENT '加密盐',
`password` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL DEFAULT '' COMMENT '用户密码',
`role` tinyint NOT NULL DEFAULT '0' COMMENT '角色:1 - 普通;2 - 管理员',
`realname` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL DEFAULT '' COMMENT '真实姓名',
`login_at` bigint NOT NULL DEFAULT '0' COMMENT '最近一次登录时间',
`login_token` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL DEFAULT '' COMMENT '当前登录的token',
`created_at` bigint NOT NULL DEFAULT '0' COMMENT '创建时间',
Expand Down Expand Up @@ -65,8 +63,8 @@ CREATE TABLE `project` (

LOCK TABLES `account` WRITE;
TRUNCATE `account`;
INSERT INTO `account` (`id`, `username`, `password`, `salt`, `role`, `realname`, `login_at`, `login_token`, `created_at`, `updated_at`) VALUES
(1,'admin','e03dcdf34a257041b36bd77132130fdc','LCV8xdTcqmkhA$ze',2,'Administrator',1675941517,'cc3e49577201323b0010815f2485acd9',1675941476,1675941517);
INSERT INTO `account` (`id`, `username`, `password`, `role`, `login_at`, `login_token`, `created_at`, `updated_at`) VALUES
(1,'admin','$2b$12$6IazZqBcVoaWonrRYgXdGO.vqVI9MgkNzxTBZwCTkjlJAf8labC7O',2,0,'',1675941476,1675941517);
UNLOCK TABLES;


Expand Down
2 changes: 1 addition & 1 deletion src/app/api/account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use crate::app::service::{
#[handler]
pub async fn create(req: &mut Request) -> ApiResult<()> {
let params = req.parse_json::<ReqCreate>().await.map_err(|e| {
tracing::error!(error = ?e, "Error req.parse_json");
tracing::error!(err = ?e, "req.parse_json");
Code::ErrParams(Some("参数解析出错".to_string()))
})?;
if let Err(e) = params.validate() {
Expand Down
2 changes: 1 addition & 1 deletion src/app/api/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use crate::app::service::{
#[handler]
pub async fn login(req: &mut Request) -> ApiResult<RespLogin> {
let params = req.parse_json::<ReqLogin>().await.map_err(|e| {
tracing::error!(error = ?e, "Error req.parse_json");
tracing::error!(err = ?e, "req.parse_json");
Code::ErrParams(Some("参数解析出错".to_string()))
})?;
if let Err(e) = params.validate() {
Expand Down
2 changes: 1 addition & 1 deletion src/app/api/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use crate::app::service::{
#[handler]
pub async fn create(req: &mut Request) -> ApiResult<()> {
let params = req.parse_json::<ReqCreate>().await.map_err(|e| {
tracing::error!(error = ?e, "Error req.parse_json");
tracing::error!(err = ?e, "req.parse_json");
Code::ErrParams(Some("参数解析出错".to_string()))
})?;
if let Err(e) = params.validate() {
Expand Down
5 changes: 4 additions & 1 deletion src/app/middleware/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ pub async fn auth_check(identity: &Identity) -> Result<()> {
match ret {
None => return Err(anyhow!("授权账号不存在")),
Some(v) => {
if v.login_token.is_empty() || !identity.match_token(v.login_token) {
if v.login_token.is_empty()
|| !identity.match_token(v.login_token)
|| identity.is_expired()
{
return Err(anyhow!("授权已失效"));
}
}
Expand Down
2 changes: 0 additions & 2 deletions src/app/model/account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@ pub struct Model {
#[sea_orm(unique)]
pub username: String,
pub password: String,
pub salt: String,
pub role: i8,
pub realname: String,
pub login_at: i64,
pub login_token: String,
pub created_at: i64,
Expand Down
26 changes: 10 additions & 16 deletions src/app/service/account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ use serde::{Deserialize, Serialize};
use validator::Validate;

use crate::shared::core::db;
use crate::shared::crypto::hash;
use crate::shared::result::code::Code;
use crate::shared::result::{reply, ApiResult};
use crate::shared::util::{helper, xtime};
Expand All @@ -19,8 +18,6 @@ pub struct ReqCreate {
pub username: String,
#[validate(length(min = 1, message = "密码必填"))]
pub password: String,
#[validate(length(min = 1, message = "实名必填"))]
pub realname: String,
}

pub async fn create(req: ReqCreate) -> ApiResult<()> {
Expand All @@ -29,29 +26,29 @@ pub async fn create(req: ReqCreate) -> ApiResult<()> {
.count(db::conn())
.await
.map_err(|e| {
tracing::error!(error = ?e, "error find account");
tracing::error!(err = ?e, "find account");
Code::ErrSystem(None)
})?;
if count > 0 {
return Err(Code::ErrPerm(Some("该用户名已被使用".to_string())));
}

let salt = helper::nonce(16);
let pass = format!("{}{}", req.password, salt);
let passwd = bcrypt::hash(req.password, bcrypt::DEFAULT_COST).map_err(|e| {
tracing::error!(err = ?e, "hash password");
Code::ErrSystem(None)
})?;
let now = xtime::now(None).unix_timestamp();
let model = account::ActiveModel {
username: Set(req.username),
password: Set(hash::md5(pass.as_bytes())),
salt: Set(salt),
password: Set(passwd),
role: Set(1),
realname: Set(req.realname),
created_at: Set(now),
updated_at: Set(now),
..Default::default()
};

if let Err(e) = Account::insert(model).exec(db::conn()).await {
tracing::error!(error = ?e, "error insert account");
tracing::error!(err = ?e, "insert account");
return Err(Code::ErrSystem(None));
}

Expand All @@ -62,7 +59,6 @@ pub async fn create(req: ReqCreate) -> ApiResult<()> {
pub struct RespInfo {
pub id: u64,
pub username: String,
pub realname: String,
pub login_at: i64,
pub login_at_str: String,
pub created_at: i64,
Expand All @@ -74,15 +70,14 @@ pub async fn info(account_id: u64) -> ApiResult<RespInfo> {
.one(db::conn())
.await
.map_err(|e| {
tracing::error!(error = ?e, "Error Account::find_by_id");
tracing::error!(err = ?e, "Account::find_by_id");
Code::ErrSystem(None)
})?
.ok_or(Code::ErrEmpty(Some("账号不存在".to_string())))?;

let resp = RespInfo {
id: model.id,
username: model.username,
realname: model.realname,
login_at: model.login_at,
login_at_str: xtime::to_string(xtime::DATE_TIME, model.login_at, None).unwrap_or_default(),
created_at: model.created_at,
Expand Down Expand Up @@ -119,7 +114,7 @@ pub async fn list(query: &MultiMap<String, String>) -> ApiResult<RespList> {
.one(db::conn())
.await
.map_err(|e| {
tracing::error!(error = ?e, "error count account");
tracing::error!(err = ?e, "count account");
Code::ErrSystem(None)
})?
.unwrap_or_default();
Expand All @@ -132,7 +127,7 @@ pub async fn list(query: &MultiMap<String, String>) -> ApiResult<RespList> {
.all(db::conn())
.await
.map_err(|e| {
tracing::error!(error = ?e, "error find account");
tracing::error!(err = ?e, "find account");
Code::ErrSystem(None)
})?;
let mut resp = RespList {
Expand All @@ -143,7 +138,6 @@ pub async fn list(query: &MultiMap<String, String>) -> ApiResult<RespList> {
let info = RespInfo {
id: model.id,
username: model.username,
realname: model.realname,
login_at: model.login_at,
login_at_str: xtime::to_string(xtime::DATE_TIME, model.login_at, None)
.unwrap_or_default(),
Expand Down
21 changes: 12 additions & 9 deletions src/app/service/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,17 @@ pub async fn login(req: ReqLogin) -> ApiResult<RespLogin> {
.one(db::conn())
.await
.map_err(|e| {
tracing::error!(error = ?e, "error find account");
tracing::error!(err = ?e, "find account");
Code::ErrSystem(None)
})?
.ok_or(Code::ErrAuth(Some("账号不存在".to_string())))?;
.ok_or(Code::ErrAuth(Some("账号或密码错误".to_string())))?;

let pass = format!("{}{}", req.password, model.salt);
if hash::md5(pass.as_bytes()) != model.password {
return Err(Code::ErrAuth(Some("密码错误".to_string())));
let valid = bcrypt::verify(req.password, &model.password).map_err(|e| {
tracing::error!(err = ?e, "verify password");
Code::ErrSystem(None)
})?;
if !valid {
return Err(Code::ErrAuth(Some("账号或密码错误".to_string())));
}

let now = xtime::now(None).unix_timestamp();
Expand All @@ -48,7 +51,7 @@ pub async fn login(req: ReqLogin) -> ApiResult<RespLogin> {
let auth_token = Identity::new(model.id, model.role, login_token.clone())
.to_auth_token()
.map_err(|e| {
tracing::error!(error = ?e, "error identity encrypt");
tracing::error!(err = ?e, "identity encrypt");
Code::ErrSystem(None)
})?;
let update_model = account::ActiveModel {
Expand All @@ -63,12 +66,12 @@ pub async fn login(req: ReqLogin) -> ApiResult<RespLogin> {
.exec(db::conn())
.await;
if let Err(e) = ret_update {
tracing::error!(error = ?e, "error update account");
tracing::error!(err = ?e, "update account");
return Err(Code::ErrSystem(None));
}

let resp = RespLogin {
name: model.realname,
name: model.username,
role: model.role,
auth_token,
};
Expand All @@ -88,7 +91,7 @@ pub async fn logout(identity: &Identity) -> ApiResult<()> {
.await;

if let Err(e) = ret {
tracing::error!(error = ?e, "error update account");
tracing::error!(err = ?e, "update account");
return Err(Code::ErrSystem(None));
}

Expand Down
12 changes: 6 additions & 6 deletions src/app/service/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ pub async fn create(id: &Identity, req: ReqCreate) -> ApiResult<()> {
.count(db::conn())
.await
.map_err(|e| {
tracing::error!(error = ?e, "error find project");
tracing::error!(err = ?e, "find project");
Code::ErrSystem(None)
})?;
if count > 0 {
Expand All @@ -48,7 +48,7 @@ pub async fn create(id: &Identity, req: ReqCreate) -> ApiResult<()> {
..Default::default()
};
if let Err(e) = Project::insert(model).exec(db::conn()).await {
tracing::error!(error = ?e, "error insert project");
tracing::error!(err = ?e, "insert project");
return Err(Code::ErrSystem(None));
}

Expand Down Expand Up @@ -104,7 +104,7 @@ pub async fn list(id: &Identity, query: &MultiMap<String, String>) -> ApiResult<
.one(db::conn())
.await
.map_err(|e| {
tracing::error!(error = ?e, "error count project");
tracing::error!(err = ?e, "count project");
Code::ErrSystem(None)
})?
.unwrap_or_default();
Expand All @@ -117,7 +117,7 @@ pub async fn list(id: &Identity, query: &MultiMap<String, String>) -> ApiResult<
.all(db::conn())
.await
.map_err(|e| {
tracing::error!(error = ?e, "error find project");
tracing::error!(err = ?e, "find project");
Code::ErrSystem(None)
})?;
let mut resp = RespList {
Expand Down Expand Up @@ -161,7 +161,7 @@ pub async fn detail(id: &Identity, project_id: u64) -> ApiResult<RespDetail> {
.one(db::conn())
.await
.map_err(|e| {
tracing::error!(error = ?e, "error find project");
tracing::error!(err = ?e, "find project");
Code::ErrSystem(None)
})?
.ok_or(Code::ErrEmpty(Some("项目不存在".to_string())))?;
Expand All @@ -181,7 +181,7 @@ pub async fn detail(id: &Identity, project_id: u64) -> ApiResult<RespDetail> {
if let Some(v) = model_account {
resp.account = Some(ProjAccount {
id: v.id,
name: v.realname,
name: v.username,
})
}

Expand Down
2 changes: 1 addition & 1 deletion src/shared/middleware/log.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ async fn drain_body(req: &mut Request) -> (Option<String>, Option<Code>) {
let bytes = match body.collect().await {
Ok(v) => v.to_bytes(),
Err(e) => {
tracing::error!(error = ?e, "Error body.collect");
tracing::error!(err = ?e, "body.collect");
return (None, Some(Code::ErrSystem(None)));
}
};
Expand Down
20 changes: 16 additions & 4 deletions src/shared/util/identity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ use std::fmt::Display;
use crate::config;
use crate::shared::crypto::aes::CBC;

use super::xtime;

pub const EXPIRE_SECONDS: i64 = 86400;

pub enum Role {
Super,
Normal,
Expand All @@ -17,6 +21,7 @@ pub struct Identity {
i: u64,
r: i8,
t: String,
x: i64,
}

impl Identity {
Expand All @@ -25,6 +30,7 @@ impl Identity {
i: id,
r: role,
t: token,
x: xtime::now(None).unix_timestamp() + EXPIRE_SECONDS,
}
}

Expand All @@ -33,6 +39,7 @@ impl Identity {
i: 0,
r: 0,
t: String::from(""),
x: 0,
}
}

Expand All @@ -42,30 +49,30 @@ impl Identity {
}
let cipher = match BASE64_STANDARD.decode(token) {
Err(e) => {
tracing::error!(error = ?e, "error invalid auth_token");
tracing::error!(err = ?e, "invalid auth_token");
return Identity::empty();
}
Ok(v) => v,
};
let secret = match config::global().get_string("app.secret") {
Err(e) => {
tracing::error!(error = ?e, "error missing config(app.secret)");
tracing::error!(err = ?e, "missing config(app.secret)");
return Identity::empty();
}
Ok(v) => v,
};
let key = secret.as_bytes();
let plain = match CBC(key, &key[..16]).decrypt(&cipher) {
Err(e) => {
tracing::error!(error = ?e, "error invalid auth_token");
tracing::error!(err = ?e, "invalid auth_token");
return Identity::empty();
}
Ok(v) => v,
};

match serde_json::from_slice::<Identity>(&plain) {
Err(e) => {
tracing::error!(error = ?e, "error invalid auth_token");
tracing::error!(err = ?e, "invalid auth_token");
Identity::empty()
}
Ok(identity) => identity,
Expand All @@ -90,6 +97,10 @@ impl Identity {
self.t == token
}

pub fn is_expired(&self) -> bool {
self.x > xtime::now(None).unix_timestamp()
}

pub fn is_role(&self, role: Role) -> bool {
match role {
Role::Normal => self.r == 1,
Expand All @@ -114,6 +125,7 @@ impl Default for Identity {
i: 0,
r: 0,
t: String::from(""),
x: 0,
}
}
}
Expand Down
Loading

0 comments on commit 9d8ba14

Please sign in to comment.