// StarKingdoms.IO, a browser game about drifting through space
// Copyright (C) 2023 ghostly_zsh, TerraMaster85, core
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use crate::models::User;
use crate::response::JsonAPIResponse;
use crate::schema::users;
use crate::tokens::UserToken;
use crate::AppState;
use actix_web::{
http::StatusCode,
post,
web::{Data, Json},
};
use argon2::Argon2;
use diesel::{ExpressionMethods, OptionalExtension, QueryDsl, SelectableHelper};
use diesel_async::RunQueryDsl;
use jwt::SignWithKey;
use password_hash::{Encoding, PasswordHash};
use serde::{Deserialize, Serialize};
use std::time::{Duration, SystemTime};
#[derive(Deserialize)]
pub struct LoginRequest {
pub username: String,
pub password: String,
}
#[derive(Serialize, Debug)]
pub struct LoginResponse {
pub token: String,
pub user: UserToken,
}
#[post("/login")]
pub async fn user_login_req(
req: Json<LoginRequest>,
state: Data<AppState>,
) -> JsonAPIResponse<LoginResponse> {
let mut conn = handle_error!(state.pool.get().await);
let user: User = match handle_error!(users::dsl::users
.filter(users::username.eq(&req.username))
.select(User::as_select())
.first(&mut conn)
.await
.optional())
{
Some(t) => t,
None => {
err!(
StatusCode::UNAUTHORIZED,
make_err!("ERR_UNAUTHORIZED", "user does not exist", "username")
);
}
};
let hash = handle_error!(PasswordHash::parse(&user.password_hash, Encoding::B64));
let pwd_ok = hash
.verify_password(&[&Argon2::default()], req.password.as_bytes())
.is_ok();
if !pwd_ok {
err!(
StatusCode::UNAUTHORIZED,
make_err!("ERR_UNAUTHORIZED", "unauthorized")
)
}
let token = UserToken {
id: user.id,
username: user.username.clone(),
permission_level: user.permission_level,
expires: SystemTime::now() + Duration::from_secs(86400 * 365), // 1 year
};
let jwt_str = handle_error!(token.clone().sign_with_key(&state.key));
ok!(LoginResponse {
token: jwt_str,
user: token
})
}