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 675a78d commit 44f4358
Show file tree
Hide file tree
Showing 12 changed files with 454 additions and 124 deletions.
415 changes: 361 additions & 54 deletions Cargo.lock

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
name = "yiirs"
version = "1.3.0"
edition = "2021"
description = "API project template for Rust"
description = "API framework for Rust"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

Expand All @@ -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
12 changes: 6 additions & 6 deletions src/app/api/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,25 +18,25 @@ use crate::app::service::{
};

pub async fn create(
Extension(identity): Extension<Identity>,
Extension(id): Extension<Identity>,
WithRejection(Json(req), _): IRejection<Json<ReqCreate>>,
) -> ApiResult<()> {
if let Err(e) = req.validate() {
return Err(Code::ErrParams(Some(e.to_string())));
}
service::project::create(identity, req).await
service::project::create(id, req).await
}

pub async fn list(
Extension(identity): Extension<Identity>,
Extension(id): Extension<Identity>,
Query(query): Query<HashMap<String, String>>,
) -> ApiResult<RespList> {
service::project::list(identity, query).await
service::project::list(id, query).await
}

pub async fn detail(
Extension(identity): Extension<Identity>,
Extension(id): Extension<Identity>,
Path(project_id): Path<u64>,
) -> ApiResult<RespDetail> {
service::project::detail(identity, project_id).await
service::project::detail(id, project_id).await
}
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 @@ -7,7 +7,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 @@ -20,8 +19,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 @@ -30,29 +27,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 @@ -63,7 +60,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 @@ -75,15 +71,14 @@ pub async fn info(account_id: u64) -> ApiResult<RespInfo> {
.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::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 @@ -120,7 +115,7 @@ pub async fn list(query: HashMap<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 @@ -133,7 +128,7 @@ pub async fn list(query: HashMap<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 @@ -144,7 +139,6 @@ pub async fn list(query: HashMap<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
25 changes: 14 additions & 11 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,22 +66,22 @@ 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,
};

Ok(reply::OK(Some(resp)))
}

pub async fn logout(identity: Identity) -> ApiResult<()> {
pub async fn logout(id: Identity) -> ApiResult<()> {
let ret = Account::update_many()
.filter(account::Column::Id.eq(identity.id()))
.filter(account::Column::Id.eq(id.id()))
.col_expr(account::Column::LoginToken, Expr::value(""))
.col_expr(
account::Column::CreatedAt,
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
26 changes: 13 additions & 13 deletions src/app/service/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,14 @@ pub struct ReqCreate {
pub remark: Option<String>,
}

pub async fn create(identity: Identity, req: ReqCreate) -> ApiResult<()> {
pub async fn create(id: Identity, req: ReqCreate) -> ApiResult<()> {
// 校验编号唯一性
let count = Project::find()
.filter(project::Column::Code.eq(req.code.clone()))
.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 @@ -43,13 +43,13 @@ pub async fn create(identity: Identity, req: ReqCreate) -> ApiResult<()> {
code: Set(req.code),
name: Set(req.name),
remark: Set(req.remark.unwrap_or_default()),
account_id: Set(identity.id()),
account_id: Set(id.id()),
created_at: Set(now),
updated_at: Set(now),
..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 All @@ -71,16 +71,16 @@ pub struct RespList {
pub list: Vec<RespInfo>,
}

pub async fn list(identity: Identity, query: HashMap<String, String>) -> ApiResult<RespList> {
pub async fn list(id: Identity, query: HashMap<String, String>) -> ApiResult<RespList> {
let mut builder = Project::find();
if identity.is_role(Role::Super) {
if id.is_role(Role::Super) {
if let Some(account_id) = query.get("account_id") {
if let Ok(v) = account_id.parse::<u64>() {
builder = builder.filter(project::Column::AccountId.eq(v));
}
}
} else {
builder = builder.filter(project::Column::AccountId.eq(identity.id()));
builder = builder.filter(project::Column::AccountId.eq(id.id()));
}
if let Some(code) = query.get("code") {
if !code.is_empty() {
Expand All @@ -105,7 +105,7 @@ pub async fn list(identity: Identity, query: HashMap<String, String>) -> ApiResu
.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 @@ -118,7 +118,7 @@ pub async fn list(identity: Identity, query: HashMap<String, String>) -> ApiResu
.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 @@ -156,17 +156,17 @@ pub struct ProjAccount {
pub name: String,
}

pub async fn detail(identity: Identity, project_id: u64) -> ApiResult<RespDetail> {
pub async fn detail(id: Identity, project_id: u64) -> ApiResult<RespDetail> {
let (model_proj, model_account) = Project::find_by_id(project_id)
.find_also_related(Account)
.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())))?;
if !identity.is_role(Role::Super) && identity.id() != model_proj.account_id {
if !id.is_role(Role::Super) && id.id() != model_proj.account_id {
return Err(Code::ErrPerm(None));
}

Expand All @@ -182,7 +182,7 @@ pub async fn detail(identity: 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 @@ -73,7 +73,7 @@ async fn drain_body(request: Request, next: Next) -> Result<(Response, Option<St
let bytes = match body.collect().await {
Ok(v) => v.to_bytes(),
Err(e) => {
tracing::error!(error = ?e, "Error body.collect");
tracing::error!(err = ?e, "Error body.collect");
return Err(Code::ErrSystem(None));
}
};
Expand Down
Loading

0 comments on commit 44f4358

Please sign in to comment.