mirror of
https://github.com/danbulant/Portfolio
synced 2026-06-17 05:21:07 +00:00
Merge pull request #17 from refresh_token_auth
This commit is contained in:
commit
98e506e5ff
20 changed files with 253 additions and 235 deletions
|
|
@ -1,33 +0,0 @@
|
|||
use rocket::http::Status;
|
||||
use rocket::outcome::Outcome;
|
||||
use rocket::request::{FromRequest, Request};
|
||||
|
||||
use portfolio_core::token::candidate_token::CandidateToken;
|
||||
use portfolio_core::token::decode_candidate_token;
|
||||
|
||||
pub struct TokenRequest(CandidateToken);
|
||||
|
||||
impl TokenRequest {
|
||||
pub fn to_token(self) -> CandidateToken {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[rocket::async_trait]
|
||||
impl<'r> FromRequest<'r> for TokenRequest {
|
||||
type Error = Status;
|
||||
async fn from_request(req: &'r Request<'_>) -> Outcome<TokenRequest, (Status, Status), ()> {
|
||||
if let Some(auth) = req.headers().get_one("Authorization") {
|
||||
let auth_string = auth.to_string();
|
||||
if auth_string.starts_with("Bearer") {
|
||||
let token = auth_string[6..auth_string.len()].trim();
|
||||
let token_data = decode_candidate_token(token.to_string());
|
||||
|
||||
if token_data.is_ok() {
|
||||
return Outcome::Success(TokenRequest(token_data.ok().unwrap().claims));
|
||||
}
|
||||
}
|
||||
}
|
||||
return Outcome::Failure((Status::Unauthorized, Status::Unauthorized));
|
||||
}
|
||||
}
|
||||
27
api/src/guard/candidate_refresh_token.rs
Normal file
27
api/src/guard/candidate_refresh_token.rs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
use portfolio_core::sea_orm::prelude::Uuid;
|
||||
use rocket::http::Status;
|
||||
use rocket::outcome::Outcome;
|
||||
use rocket::request::{FromRequest, Request};
|
||||
|
||||
|
||||
pub struct UUIDCookie(Uuid);
|
||||
|
||||
impl UUIDCookie {
|
||||
pub fn value(self) -> Uuid {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[rocket::async_trait]
|
||||
impl<'r> FromRequest<'r> for UUIDCookie {
|
||||
type Error = Status;
|
||||
async fn from_request(req: &'r Request<'_>) -> Outcome<UUIDCookie, (Status, Status), ()> {
|
||||
let session_id = req.cookies().get("id").unwrap().name_value().1;
|
||||
println!("session_id: {}", session_id);
|
||||
|
||||
match Uuid::parse_str(&session_id) {
|
||||
Ok(uuid) => Outcome::Success(UUIDCookie(uuid)),
|
||||
Err(_) => return Outcome::Failure((Status::BadRequest, Status::BadRequest)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +1 @@
|
|||
pub mod candidate_jwt;
|
||||
pub mod candidate_refresh_token;
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
#[macro_use]
|
||||
extern crate rocket;
|
||||
|
||||
use guard::candidate_jwt::TokenRequest;
|
||||
use portfolio_core::error::ServiceError;
|
||||
use portfolio_core::services::candidate_service::CandidateService;
|
||||
use requests::LoginRequest;
|
||||
|
|
@ -27,6 +26,8 @@ pub use entity::candidate::Entity as Candidate;
|
|||
|
||||
use portfolio_core::crypto::random_8_char_string;
|
||||
|
||||
use crate::guard::candidate_refresh_token::UUIDCookie;
|
||||
|
||||
fn custom_err_from_service_err(service_err: ServiceError) -> Custom<String> {
|
||||
Custom(Status::from_code(service_err.0.code).unwrap_or_default(), service_err.1.to_string())
|
||||
}
|
||||
|
|
@ -45,38 +46,39 @@ async fn create(conn: Connection<'_, Db>, post_form: Json<candidate::Model>) ->
|
|||
Ok(plain_text_password)
|
||||
}
|
||||
|
||||
#[get("/whoami")]
|
||||
async fn validate(conn: Connection<'_, Db>, uuid_cookie: Result<UUIDCookie, Status>) -> Result<String, Custom<String>> {
|
||||
let db = conn.into_inner();
|
||||
let user = CandidateService::auth_user_session(db, uuid_cookie.ok().unwrap().value()).await;
|
||||
|
||||
|
||||
match user {
|
||||
Ok(user) => Ok(user.application.to_string()),
|
||||
Err(err) => Err(custom_err_from_service_err(err))
|
||||
}
|
||||
}
|
||||
|
||||
#[post("/login", data = "<login_form>")]
|
||||
async fn login(conn: Connection<'_, Db>, login_form: Json<LoginRequest>) -> Result<String, Custom<String>> {
|
||||
let db = conn.into_inner();
|
||||
println!("{} {}", login_form.application_id, login_form.password);
|
||||
|
||||
let jwt = CandidateService::login(db,
|
||||
login_form.application_id,
|
||||
login_form.password.to_owned()).await;
|
||||
let session_token = CandidateService::new_session(db,
|
||||
login_form.application_id,
|
||||
login_form.password.to_string()
|
||||
).await;
|
||||
|
||||
if jwt.is_ok() {
|
||||
if session_token.is_ok() {
|
||||
return Ok(
|
||||
jwt.ok().unwrap()
|
||||
session_token.ok().unwrap()
|
||||
);
|
||||
} else {
|
||||
return Err(
|
||||
custom_err_from_service_err(jwt.err().unwrap())
|
||||
custom_err_from_service_err(session_token.err().unwrap())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/whoami")]
|
||||
async fn whoami(conn: Connection<'_, Db>, token_req: Result<TokenRequest, Status>) -> Result<String, Custom<String>> {
|
||||
let db = conn.into_inner();
|
||||
let token = token_req.ok().unwrap().to_token();
|
||||
let user = CandidateService::authenticate_candidate(db, token).await;
|
||||
|
||||
match user {
|
||||
Ok(user) => Ok(format!("{} {}", user.name.unwrap(), user.surname.unwrap())),
|
||||
Err(e) => Err(custom_err_from_service_err(e)),
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/hello")]
|
||||
async fn hello() -> &'static str {
|
||||
"Hello, world!"
|
||||
|
|
@ -94,7 +96,7 @@ async fn start() -> Result<(), rocket::Error> {
|
|||
.attach(Db::init())
|
||||
.attach(AdHoc::try_on_ignite("Migrations", run_migrations))
|
||||
//.mount("/", FileServer::from(relative!("/static")))
|
||||
.mount("/", routes![create, login, hello, whoami])
|
||||
.mount("/", routes![create, login, hello, validate])
|
||||
.register("/", catchers![])
|
||||
.launch()
|
||||
.await
|
||||
|
|
|
|||
|
|
@ -461,4 +461,4 @@ mod tests {
|
|||
PASSWORD
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,9 @@ pub struct Status {
|
|||
|
||||
pub const INVALID_CREDENTIALS_ERROR: ServiceError = ServiceError(Status { code: 401 },
|
||||
"Invalid credentials");
|
||||
pub const EXPIRED_SESSION_ERROR: ServiceError = ServiceError(Status { code: 401 },
|
||||
"Session expired, please login again");
|
||||
|
||||
pub const JWT_ERROR: ServiceError = ServiceError(Status { code: 500 },
|
||||
"Error while encoding JWT");
|
||||
|
||||
|
|
@ -13,6 +16,9 @@ pub const USER_NOT_FOUND_ERROR: ServiceError = ServiceError(Status { code: 404 }
|
|||
pub const DB_ERROR: ServiceError = ServiceError(Status { code: 500 },
|
||||
"Database error");
|
||||
|
||||
pub const USER_NOT_FOUND_BY_JWT_ID: ServiceError = ServiceError(Status { code: 500 }, // User got somehow
|
||||
pub const USER_NOT_FOUND_BY_JWT_ID: ServiceError = ServiceError(Status { code: 500 }, // User got somehow deleted
|
||||
"User not found, please contact technical support"); // Shouldn't ever happen
|
||||
|
||||
pub const USER_NOT_FOUND_BY_SESSION_ID: ServiceError = ServiceError(Status { code: 500 }, // User got somehow deleted
|
||||
"User not found, please contact technical support"); // Shouldn't ever happen
|
||||
pub struct ServiceError<'a>(pub Status, pub &'a str);
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
mod mutation;
|
||||
mod query;
|
||||
pub mod crypto;
|
||||
pub mod token;
|
||||
pub mod filetype;
|
||||
pub mod services;
|
||||
pub mod error;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use ::entity::{candidate};
|
||||
use sea_orm::*;
|
||||
use chrono::{Utc, Duration};
|
||||
use ::entity::{candidate, session};
|
||||
use sea_orm::{*, prelude::Uuid};
|
||||
use crate::crypto::hash_password;
|
||||
|
||||
pub struct Mutation;
|
||||
|
|
@ -24,4 +25,33 @@ impl Mutation {
|
|||
.insert(db)
|
||||
.await
|
||||
}
|
||||
|
||||
|
||||
pub async fn insert_session(
|
||||
db: &DbConn,
|
||||
user_id: i32,
|
||||
random_uuid: Uuid,
|
||||
) -> Result<session::Model, DbErr> {
|
||||
session::ActiveModel {
|
||||
id: Set(random_uuid),
|
||||
user_id: Set(user_id),
|
||||
ip_address: Set("127.0.0.1".to_string()),
|
||||
created_at: Set(Utc::now().naive_local()),
|
||||
expires_at: Set(Utc::now().naive_local().checked_add_signed(Duration::days(1)).unwrap()),
|
||||
}
|
||||
.insert(db)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn delete_session(
|
||||
db: &DbConn,
|
||||
session_id: Uuid
|
||||
) -> Result<DeleteResult, DbErr> {
|
||||
session::ActiveModel {
|
||||
id: Set(session_id),
|
||||
..Default::default()
|
||||
}
|
||||
.delete(db)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
use ::entity::{candidate, candidate::Entity as Candidate};
|
||||
use ::entity::{session, session::Entity as Session};
|
||||
use sea_orm::*;
|
||||
use sea_orm::prelude::Uuid;
|
||||
|
||||
pub struct Query;
|
||||
|
||||
|
|
@ -7,6 +9,18 @@ impl Query {
|
|||
pub async fn find_candidate_by_id(db: &DbConn, id: i32) -> Result<Option<candidate::Model>, DbErr> {
|
||||
Candidate::find_by_id(id).one(db).await
|
||||
}
|
||||
|
||||
pub async fn find_session_by_uuid(db: &DbConn, uuid: Uuid) -> Result<Option<session::Model>, DbErr> {
|
||||
Session::find_by_id(uuid).one(db).await
|
||||
}
|
||||
|
||||
// find session by user id
|
||||
pub async fn find_session_by_user_id(db: &DbConn, user_id: i32) -> Result<Option<session::Model>, DbErr> {
|
||||
Session::find()
|
||||
.filter(session::Column::UserId.eq(user_id))
|
||||
.one(db)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
|||
|
|
@ -1,36 +1,63 @@
|
|||
use chrono::Duration;
|
||||
use entity::candidate;
|
||||
use sea_orm::DatabaseConnection;
|
||||
use sea_orm::{DatabaseConnection, prelude::Uuid, ModelTrait};
|
||||
|
||||
use crate::{crypto, Query, token::{generate_candidate_token, candidate_token::CandidateToken}, error::{ServiceError, USER_NOT_FOUND_ERROR, INVALID_CREDENTIALS_ERROR, DB_ERROR, USER_NOT_FOUND_BY_JWT_ID}};
|
||||
use crate::{crypto::{self}, Query, error::{ServiceError, USER_NOT_FOUND_ERROR, INVALID_CREDENTIALS_ERROR, DB_ERROR, USER_NOT_FOUND_BY_JWT_ID, USER_NOT_FOUND_BY_SESSION_ID}, Mutation};
|
||||
|
||||
pub struct CandidateService;
|
||||
|
||||
impl CandidateService {
|
||||
|
||||
pub async fn login(db: &DatabaseConnection, id: i32, password: String) -> Result<String, ServiceError> {
|
||||
let candidate = match Query::find_candidate_by_id(db, id).await {
|
||||
pub async fn new_session(db: &DatabaseConnection, user_id: i32, password: String) -> Result<String, ServiceError> {
|
||||
let candidate = match Query::find_candidate_by_id(db, user_id).await {
|
||||
Ok(candidate) => match candidate {
|
||||
Some(candidate) => candidate,
|
||||
None => return Err(USER_NOT_FOUND_ERROR)
|
||||
},
|
||||
Err(_) => {return Err(DB_ERROR)}
|
||||
};
|
||||
|
||||
|
||||
let valid = crypto::verify_password(password,candidate.code.clone()).await
|
||||
.expect("Invalid password");
|
||||
|
||||
if !valid {
|
||||
return Err(INVALID_CREDENTIALS_ERROR)
|
||||
|
||||
// compare passwords
|
||||
match crypto::verify_password(password,candidate.code.clone()).await {
|
||||
Ok(valid) => {
|
||||
if !valid {
|
||||
return Err(INVALID_CREDENTIALS_ERROR)
|
||||
}
|
||||
},
|
||||
Err(_) => {return Err(INVALID_CREDENTIALS_ERROR)}
|
||||
}
|
||||
|
||||
let jwt = generate_candidate_token(candidate); // TODO better error handling
|
||||
Ok(jwt)
|
||||
|
||||
// TODO delete old sessions?
|
||||
|
||||
// user is authenticated, generate a session
|
||||
let random_uuid: Uuid = Uuid::new_v4();
|
||||
|
||||
let session = match Mutation::insert_session(db, user_id, random_uuid).await {
|
||||
Ok(session) => session,
|
||||
Err(_) => return Err(DB_ERROR)
|
||||
};
|
||||
|
||||
Ok(session.id.to_string())
|
||||
}
|
||||
|
||||
pub async fn authenticate_candidate(db: &DatabaseConnection, token: CandidateToken) -> Result<candidate::Model, ServiceError> {
|
||||
let candidate = match Query::find_candidate_by_id(db, token.application_id).await {
|
||||
pub async fn auth_user_session(db: &DatabaseConnection, uuid: Uuid) -> Result<candidate::Model, ServiceError> {
|
||||
let session = match Query::find_session_by_uuid(db, uuid).await {
|
||||
Ok(session) => match session {
|
||||
Some(session) => session,
|
||||
None => return Err(USER_NOT_FOUND_BY_SESSION_ID)
|
||||
},
|
||||
Err(_) => {return Err(DB_ERROR)}
|
||||
};
|
||||
|
||||
let limit = session.created_at.checked_add_signed(Duration::days(1)).unwrap();
|
||||
let now = chrono::Utc::now().naive_utc();
|
||||
// check if session is expired
|
||||
if now > limit {
|
||||
// delete session
|
||||
Mutation::delete_session(db, session.id).await.unwrap();
|
||||
return Err(USER_NOT_FOUND_BY_SESSION_ID)
|
||||
}
|
||||
|
||||
let candidate = match session.find_related(candidate::Entity).one(db).await {
|
||||
Ok(candidate) => match candidate {
|
||||
Some(candidate) => candidate,
|
||||
None => return Err(USER_NOT_FOUND_BY_JWT_ID)
|
||||
|
|
@ -39,7 +66,7 @@ impl CandidateService {
|
|||
};
|
||||
|
||||
Ok(candidate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -47,19 +74,23 @@ impl CandidateService {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use entity::candidate;
|
||||
use sea_orm::{DbConn, Database, sea_query::TableCreateStatement, DbBackend, Schema, ConnectionTrait};
|
||||
use sea_orm::{DbConn, Database, sea_query::TableCreateStatement, DbBackend, Schema, ConnectionTrait, prelude::Uuid};
|
||||
use serde_json::json;
|
||||
|
||||
use crate::{crypto, Mutation, services::candidate_service::CandidateService, token};
|
||||
use crate::{crypto, Mutation, services::candidate_service::CandidateService};
|
||||
|
||||
#[cfg(test)]
|
||||
async fn get_memory_sqlite_connection() -> DbConn {
|
||||
use entity::session;
|
||||
|
||||
let base_url = "sqlite::memory:";
|
||||
let db: DbConn = Database::connect(base_url).await.unwrap();
|
||||
|
||||
let schema = Schema::new(DbBackend::Sqlite);
|
||||
let stmt: TableCreateStatement = schema.create_table_from_entity(candidate::Entity);
|
||||
let stmt2: TableCreateStatement = schema.create_table_from_entity(session::Entity);
|
||||
db.execute(db.get_database_backend().build(&stmt)).await.unwrap();
|
||||
db.execute(db.get_database_backend().build(&stmt2)).await.unwrap();
|
||||
db
|
||||
}
|
||||
|
||||
|
|
@ -79,22 +110,46 @@ mod tests {
|
|||
assert_ne!(candidate.code, SECRET.to_string());
|
||||
assert!(crypto::verify_password(SECRET.to_string(), candidate.code).await.ok().unwrap());
|
||||
}
|
||||
|
||||
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_candidate_jwt() {
|
||||
async fn test_candidate_session_correct_password() {
|
||||
let db = &get_memory_sqlite_connection().await;
|
||||
|
||||
|
||||
let form = serde_json::from_value(json!({
|
||||
"application": 5555555,
|
||||
})).unwrap();
|
||||
|
||||
let candidate = Mutation::create_candidate(&db, form, &"Tajny_kod".to_string()).await.unwrap();
|
||||
|
||||
let jwt = CandidateService::login(db, 5555555, "Tajny_kod".to_string()).await.ok().unwrap();
|
||||
|
||||
let claims = token::decode_candidate_token(jwt).ok().unwrap().claims;
|
||||
|
||||
assert_eq!(claims.application_id, candidate.application);
|
||||
|
||||
Mutation::create_candidate(&db, form, &"Tajny_kod".to_string()).await.unwrap();
|
||||
|
||||
// correct password
|
||||
let session = CandidateService::new_session(
|
||||
db,
|
||||
5555555,
|
||||
"Tajny_kod".to_string()
|
||||
)
|
||||
.await.ok().unwrap();
|
||||
// println!("{}", session.err().unwrap().1);
|
||||
|
||||
assert!(
|
||||
CandidateService::auth_user_session(db, Uuid::parse_str(&session).unwrap())
|
||||
.await
|
||||
.is_ok()
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_candidate_session_incorrect_password() {
|
||||
let db = &get_memory_sqlite_connection().await;
|
||||
|
||||
let form = serde_json::from_value(json!({
|
||||
"application": 5555555,
|
||||
})).unwrap();
|
||||
|
||||
let candidate_form = Mutation::create_candidate(&db, form, &"Tajny_kod".to_string()).await.unwrap();
|
||||
|
||||
// incorrect password
|
||||
assert!(
|
||||
CandidateService::new_session(db, candidate_form.application, "Spatny_kod".to_string()).await.is_err()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
use serde::{Serialize, Deserialize};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct AdminToken {
|
||||
// issued at
|
||||
pub iat: i64,
|
||||
// expiration
|
||||
pub exp: i64,
|
||||
}
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
use chrono::Utc;
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct CandidateToken {
|
||||
// issued at
|
||||
pub iat: i64,
|
||||
// expiration
|
||||
pub exp: i64,
|
||||
pub application_id: i32,
|
||||
pub name: String,
|
||||
pub surname: String,
|
||||
}
|
||||
|
||||
impl CandidateToken {
|
||||
pub fn generate(application_id: i32, name: String, surname: String) -> Self {
|
||||
let now = Utc::now().timestamp();
|
||||
CandidateToken {
|
||||
iat: now,
|
||||
exp: now + 60 * 60, // 1 hour for now
|
||||
application_id,
|
||||
name,
|
||||
surname,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,98 +0,0 @@
|
|||
pub mod admin_token;
|
||||
pub mod candidate_token;
|
||||
|
||||
use chrono::Utc;
|
||||
|
||||
use entity::{admin, candidate};
|
||||
use jsonwebtoken::errors::Result;
|
||||
use jsonwebtoken::TokenData;
|
||||
use jsonwebtoken::{DecodingKey, EncodingKey};
|
||||
use jsonwebtoken::{Header, Validation};
|
||||
|
||||
use admin_token::AdminToken;
|
||||
use candidate_token::CandidateToken;
|
||||
use serde::Deserialize;
|
||||
|
||||
const ONE_WEEK: i64 = 60 * 60 * 24 * 7;
|
||||
|
||||
pub fn generate_candidate_token(candidate: candidate::Model) -> String {
|
||||
let now = Utc::now().timestamp();
|
||||
let payload = CandidateToken {
|
||||
iat: now,
|
||||
exp: now + ONE_WEEK,
|
||||
application_id: candidate.application,
|
||||
name: candidate.name.unwrap_or_else(|| "".into()),
|
||||
surname: candidate.surname.unwrap_or_else(|| "".into()),
|
||||
};
|
||||
|
||||
jsonwebtoken::encode(
|
||||
&Header::default(),
|
||||
&payload,
|
||||
&EncodingKey::from_secret(include_bytes!("secret.key")),
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn generate_admin_token(_admin: admin::Model) -> String {
|
||||
let now = Utc::now().timestamp();
|
||||
let payload = AdminToken {
|
||||
iat: now,
|
||||
exp: now + ONE_WEEK,
|
||||
};
|
||||
|
||||
jsonwebtoken::encode(
|
||||
&Header::default(),
|
||||
&payload,
|
||||
&EncodingKey::from_secret(include_bytes!("secret.key")),
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn decode_token<T: for<'a> Deserialize<'a>>(token: String) -> Result<TokenData<T>> {
|
||||
jsonwebtoken::decode::<T>(
|
||||
&token,
|
||||
&DecodingKey::from_secret(include_bytes!("secret.key")),
|
||||
&Validation::default(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn decode_candidate_token(token: String) -> Result<TokenData<CandidateToken>> {
|
||||
decode_token(token)
|
||||
}
|
||||
|
||||
pub fn decode_admin_token(token: String) -> Result<TokenData<AdminToken>> {
|
||||
decode_token(token)
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn test_encode_decode_token() {
|
||||
let candidate_model = candidate::Model {
|
||||
application: 101204,
|
||||
code: "random_code".to_string(),
|
||||
birth_surname: None,
|
||||
birthplace: None,
|
||||
birthdate: None,
|
||||
address: None,
|
||||
telephone: None,
|
||||
citizenship: None,
|
||||
sex: None,
|
||||
study: None,
|
||||
personal_identification_number: None,
|
||||
personal_identification_number_hash: None,
|
||||
public_key: "None".to_owned(),
|
||||
private_key: "None".to_owned(),
|
||||
created_at: Utc::now().naive_local(),
|
||||
updated_at: Utc::now().naive_local(),
|
||||
name: Some("Uplnej".to_string()),
|
||||
surname: Some("Magor".to_string()),
|
||||
email: Some("email.uchazece@centrum.cz".to_string()),
|
||||
};
|
||||
|
||||
let jwt = generate_candidate_token(candidate_model.clone());
|
||||
|
||||
let decoded = decode_candidate_token(jwt).unwrap();
|
||||
let token_claims = decoded.claims;
|
||||
assert_eq!(candidate_model.name.unwrap(), token_claims.name);
|
||||
assert_eq!(candidate_model.surname.unwrap(), token_claims.surname);
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
temp
|
||||
|
|
@ -37,6 +37,15 @@ pub struct Model {
|
|||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {}
|
||||
pub enum Relation {
|
||||
#[sea_orm(has_many = "super::session::Entity")]
|
||||
Session,
|
||||
}
|
||||
|
||||
impl Related<super::session::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Session.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
|
|
|
|||
|
|
@ -7,13 +7,28 @@ use sea_orm::entity::prelude::*;
|
|||
pub struct Model {
|
||||
#[sea_orm(primary_key, auto_increment = false)]
|
||||
pub id: Uuid,
|
||||
pub hashed_token: String,
|
||||
pub user_id: i32,
|
||||
pub ip_address: String,
|
||||
pub created_at: DateTime,
|
||||
pub updated_at: DateTime,
|
||||
pub expires_at: DateTime,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {}
|
||||
pub enum Relation {
|
||||
#[sea_orm(
|
||||
belongs_to = "super::candidate::Entity",
|
||||
from = "Column::UserId",
|
||||
to = "super::candidate::Column::Application",
|
||||
on_update = "Cascade",
|
||||
on_delete = "Cascade"
|
||||
)]
|
||||
Candidate,
|
||||
}
|
||||
|
||||
impl Related<super::candidate::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Candidate.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ mod m20221024_121621_create_candidate;
|
|||
mod m20221024_124701_create_parent;
|
||||
mod m20221024_134454_fill_admin;
|
||||
mod m20221025_154422_create_session;
|
||||
mod m20221027_194728_session_create_user_fk;
|
||||
|
||||
pub struct Migrator;
|
||||
|
||||
|
|
@ -17,6 +18,7 @@ impl MigratorTrait for Migrator {
|
|||
Box::new(m20221024_124701_create_parent::Migration),
|
||||
Box::new(m20221024_134454_fill_admin::Migration::default()),
|
||||
Box::new(m20221025_154422_create_session::Migration),
|
||||
Box::new(m20221027_194728_session_create_user_fk::Migration),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ impl MigrationTrait for Migration {
|
|||
|
||||
/// Learn more at https://docs.rs/sea-query#iden
|
||||
#[derive(Iden)]
|
||||
enum Candidate {
|
||||
pub enum Candidate {
|
||||
Table,
|
||||
Application,
|
||||
Code,
|
||||
|
|
|
|||
|
|
@ -18,10 +18,10 @@ impl MigrationTrait for Migration {
|
|||
.unique_key()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(Session::HashedToken).string().not_null())
|
||||
.col(ColumnDef::new(Session::UserId).integer().not_null())
|
||||
.col(ColumnDef::new(Session::IpAddress).string().not_null())
|
||||
.col(ColumnDef::new(Session::CreatedAt).date_time().not_null())
|
||||
.col(ColumnDef::new(Session::UpdatedAt).date_time().not_null())
|
||||
.col(ColumnDef::new(Session::ExpiresAt).date_time().not_null())
|
||||
.to_owned(),
|
||||
)
|
||||
.await
|
||||
|
|
@ -36,11 +36,11 @@ impl MigrationTrait for Migration {
|
|||
|
||||
/// Learn more at https://docs.rs/sea-query#iden
|
||||
#[derive(Iden)]
|
||||
enum Session {
|
||||
pub enum Session {
|
||||
Table,
|
||||
Id,
|
||||
HashedToken,
|
||||
UserId,
|
||||
IpAddress,
|
||||
CreatedAt,
|
||||
UpdatedAt
|
||||
ExpiresAt,
|
||||
}
|
||||
|
|
|
|||
26
migration/src/m20221027_194728_session_create_user_fk.rs
Normal file
26
migration/src/m20221027_194728_session_create_user_fk.rs
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
use sea_orm_migration::prelude::*;
|
||||
|
||||
use crate::{m20221025_154422_create_session::Session, m20221024_121621_create_candidate::Candidate};
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager.create_foreign_key(ForeignKey::create()
|
||||
.name("user_fk")
|
||||
.from(Session::Table, Session::UserId)
|
||||
.to(Candidate::Table, Candidate::Application)
|
||||
.on_delete(ForeignKeyAction::Cascade)
|
||||
.on_update(ForeignKeyAction::Cascade)
|
||||
.to_owned()).await
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager.drop_foreign_key(ForeignKey::drop()
|
||||
.name("user_fk")
|
||||
.table(Session::Table)
|
||||
.to_owned()).await
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue