Add example protected route to project
This commit is contained in:
parent
1e83393ad6
commit
fb5aa5ee5a
9 changed files with 161 additions and 36 deletions
|
@ -1,34 +1,13 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
db_connection::DbPool,
|
db_connection::DbPool,
|
||||||
errors::CustomError,
|
errors::CustomError,
|
||||||
|
handlers::logged_user::LoggedUser,
|
||||||
handlers::pool_handler,
|
handlers::pool_handler,
|
||||||
models::user::{AuthUser, DeleteUser, RegisterUser, User},
|
models::user::{AuthUser, DeleteUser, RegisterUser, User},
|
||||||
utils::jwt::{decode_token, encode_token, UserWithToken},
|
utils::jwt::encode_token,
|
||||||
};
|
};
|
||||||
use actix_identity::Identity;
|
use actix_identity::Identity;
|
||||||
use actix_web::{dev::Payload, web, FromRequest, HttpRequest, HttpResponse};
|
use actix_web::{web, HttpResponse};
|
||||||
use futures::future::Future;
|
|
||||||
use std::pin::Pin;
|
|
||||||
|
|
||||||
pub type LoggedUser = UserWithToken;
|
|
||||||
|
|
||||||
impl FromRequest for LoggedUser {
|
|
||||||
type Error = HttpResponse;
|
|
||||||
type Config = ();
|
|
||||||
type Future = Pin<Box<dyn Future<Output = Result<UserWithToken, HttpResponse>>>>;
|
|
||||||
|
|
||||||
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
|
|
||||||
let fut = Identity::from_request(req, payload);
|
|
||||||
|
|
||||||
Box::pin(async move {
|
|
||||||
if let Some(identity) = fut.await?.identity() {
|
|
||||||
let user = decode_token(&identity)?;
|
|
||||||
return Ok(user);
|
|
||||||
};
|
|
||||||
Err(HttpResponse::Unauthorized().finish())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn register(
|
pub async fn register(
|
||||||
new_user: web::Json<RegisterUser>,
|
new_user: web::Json<RegisterUser>,
|
||||||
|
|
25
project/backend/src/handlers/logged_user.rs
Normal file
25
project/backend/src/handlers/logged_user.rs
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
use crate::utils::jwt::{decode_token, UserWithToken};
|
||||||
|
use actix_identity::Identity;
|
||||||
|
use actix_web::{dev::Payload, FromRequest, HttpRequest, HttpResponse};
|
||||||
|
use futures::future::Future;
|
||||||
|
use std::pin::Pin;
|
||||||
|
|
||||||
|
pub type LoggedUser = UserWithToken;
|
||||||
|
|
||||||
|
impl FromRequest for LoggedUser {
|
||||||
|
type Error = HttpResponse;
|
||||||
|
type Config = ();
|
||||||
|
type Future = Pin<Box<dyn Future<Output = Result<UserWithToken, HttpResponse>>>>;
|
||||||
|
|
||||||
|
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
|
||||||
|
let fut = Identity::from_request(req, payload);
|
||||||
|
|
||||||
|
Box::pin(async move {
|
||||||
|
if let Some(identity) = fut.await?.identity() {
|
||||||
|
let user = decode_token(&identity)?;
|
||||||
|
return Ok(user);
|
||||||
|
};
|
||||||
|
Err(HttpResponse::Unauthorized().finish())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,6 +2,8 @@ use crate::db_connection::{DbPool, MyPooledConnection};
|
||||||
use actix_web::{web, HttpResponse};
|
use actix_web::{web, HttpResponse};
|
||||||
|
|
||||||
pub mod authentication;
|
pub mod authentication;
|
||||||
|
pub mod logged_user;
|
||||||
|
pub mod protected;
|
||||||
|
|
||||||
pub fn pool_handler(pool: web::Data<DbPool>) -> Result<MyPooledConnection, HttpResponse> {
|
pub fn pool_handler(pool: web::Data<DbPool>) -> Result<MyPooledConnection, HttpResponse> {
|
||||||
pool.get()
|
pool.get()
|
||||||
|
|
14
project/backend/src/handlers/protected.rs
Normal file
14
project/backend/src/handlers/protected.rs
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
use crate::handlers::logged_user::LoggedUser;
|
||||||
|
use actix_web::HttpResponse;
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct SuccessMessage {
|
||||||
|
message: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn protected_route(_user: LoggedUser) -> Result<HttpResponse, HttpResponse> {
|
||||||
|
Ok(HttpResponse::Ok().json(SuccessMessage {
|
||||||
|
message: String::from("Tämä on suojattu viesti palvelimelta"),
|
||||||
|
}))
|
||||||
|
}
|
|
@ -22,7 +22,7 @@ use actix_web::{
|
||||||
use chrono::Duration;
|
use chrono::Duration;
|
||||||
use db_connection::get_pool;
|
use db_connection::get_pool;
|
||||||
use dotenv::dotenv;
|
use dotenv::dotenv;
|
||||||
use handlers::authentication;
|
use handlers::{authentication, protected};
|
||||||
|
|
||||||
pub fn get_env(var_name: &str) -> String {
|
pub fn get_env(var_name: &str) -> String {
|
||||||
match std::env::var(&var_name) {
|
match std::env::var(&var_name) {
|
||||||
|
@ -96,6 +96,7 @@ async fn main() -> std::io::Result<()> {
|
||||||
.service(resource("/logout").route(post().to(authentication::logout)))
|
.service(resource("/logout").route(post().to(authentication::logout)))
|
||||||
.service(resource("/delete").route(delete().to(authentication::delete))),
|
.service(resource("/delete").route(delete().to(authentication::delete))),
|
||||||
)
|
)
|
||||||
|
.service(resource("/api/protected").route(get().to(protected::protected_route)))
|
||||||
.service(api_404_unconfigured)
|
.service(api_404_unconfigured)
|
||||||
.service(Files::new("/", "./static").index_file("index.html"))
|
.service(Files::new("/", "./static").index_file("index.html"))
|
||||||
.default_service(get().to(serve_index_html))
|
.default_service(get().to(serve_index_html))
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
use crate::utils::cookie;
|
|
||||||
use log::{error, info};
|
use log::{error, info};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use yew::format::Json;
|
use yew::format::Json;
|
||||||
|
@ -50,7 +49,6 @@ impl LoginComponent {
|
||||||
if meta.status.is_success() {
|
if meta.status.is_success() {
|
||||||
Msg::FetchReady(body.unwrap())
|
Msg::FetchReady(body.unwrap())
|
||||||
} else {
|
} else {
|
||||||
error!("{}", body.unwrap());
|
|
||||||
Msg::FetchError
|
Msg::FetchError
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -97,9 +95,6 @@ impl Component for LoginComponent {
|
||||||
Msg::FetchReady(response) => {
|
Msg::FetchReady(response) => {
|
||||||
self.fetching = false;
|
self.fetching = false;
|
||||||
info!("Login successful: {}", response);
|
info!("Login successful: {}", response);
|
||||||
cookie::get("thesis")
|
|
||||||
.map(|cookie| info!("Cookie: {}", cookie))
|
|
||||||
.map_err(|e| error!("{}", e));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Msg::FetchError => {
|
Msg::FetchError => {
|
||||||
|
@ -121,16 +116,16 @@ impl Component for LoginComponent {
|
||||||
|
|
||||||
html! {
|
html! {
|
||||||
<div class="uk-card uk-card-default uk-card-body uk-width-1-3@s uk-position-center">
|
<div class="uk-card uk-card-default uk-card-body uk-width-1-3@s uk-position-center">
|
||||||
<h1 class="uk-card-title">{ "Please log in" }</h1>
|
<h1 class="uk-card-title">{ "Kirjaudu sisään" }</h1>
|
||||||
<div>
|
<div>
|
||||||
<fieldset class="uk-fieldset">
|
<fieldset class="uk-fieldset">
|
||||||
<input class="uk-input uk-margin",
|
<input class="uk-input uk-margin",
|
||||||
placeholder="Username",
|
placeholder="Käyttäjänimi",
|
||||||
disabled=self.fetching,
|
disabled=self.fetching,
|
||||||
value=&self.username,
|
value=&self.username,
|
||||||
oninput=oninput_username, />
|
oninput=oninput_username, />
|
||||||
<input class="uk-input uk-margin-bottom",
|
<input class="uk-input uk-margin-bottom",
|
||||||
placeholder="Password",
|
placeholder="Salasana",
|
||||||
disabled=self.fetching,
|
disabled=self.fetching,
|
||||||
type="password",
|
type="password",
|
||||||
value=&self.password,
|
value=&self.password,
|
||||||
|
@ -140,7 +135,7 @@ impl Component for LoginComponent {
|
||||||
type="button",
|
type="button",
|
||||||
disabled=self.fetching,
|
disabled=self.fetching,
|
||||||
onclick=onclick>
|
onclick=onclick>
|
||||||
{ "Log in" }
|
{ "Kirjaudu" }
|
||||||
</button>
|
</button>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
pub mod login;
|
pub mod login;
|
||||||
|
pub mod protected;
|
||||||
|
|
104
project/frontend/src/component/protected.rs
Normal file
104
project/frontend/src/component/protected.rs
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
use log::{error, info};
|
||||||
|
use yew::format::Nothing;
|
||||||
|
use yew::prelude::*;
|
||||||
|
use yew::services::fetch::{FetchService, FetchTask, Request, Response};
|
||||||
|
|
||||||
|
pub struct ProtectedComponent {
|
||||||
|
component_link: ComponentLink<ProtectedComponent>,
|
||||||
|
fetch_service: FetchService,
|
||||||
|
fetch_task: Option<FetchTask>,
|
||||||
|
fetching: bool,
|
||||||
|
data: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Msg {
|
||||||
|
FetchData(),
|
||||||
|
FetchReady(String),
|
||||||
|
FetchError,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProtectedComponent {
|
||||||
|
fn get_data(&mut self) {
|
||||||
|
self.fetching = true;
|
||||||
|
|
||||||
|
let request = Request::get("http://localhost:3880/api/protected")
|
||||||
|
.body(Nothing)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
info!("Request: {:?}", request);
|
||||||
|
|
||||||
|
let callback =
|
||||||
|
self.component_link
|
||||||
|
.callback(|response: Response<Result<String, anyhow::Error>>| {
|
||||||
|
let (meta, body) = response.into_parts();
|
||||||
|
info!("{}", meta.status);
|
||||||
|
if meta.status.is_success() {
|
||||||
|
Msg::FetchReady(body.unwrap())
|
||||||
|
} else {
|
||||||
|
Msg::FetchError
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let task = self.fetch_service.fetch(request, callback);
|
||||||
|
self.fetch_task = Some(task.unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for ProtectedComponent {
|
||||||
|
type Message = Msg;
|
||||||
|
type Properties = ();
|
||||||
|
|
||||||
|
fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self {
|
||||||
|
Self {
|
||||||
|
component_link: link,
|
||||||
|
fetch_service: FetchService::new(),
|
||||||
|
fetch_task: None,
|
||||||
|
fetching: false,
|
||||||
|
data: String::from(""),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn change(&mut self, _: Self::Properties) -> ShouldRender {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, msg: Self::Message) -> ShouldRender {
|
||||||
|
match msg {
|
||||||
|
Msg::FetchReady(response) => {
|
||||||
|
self.fetching = false;
|
||||||
|
info!("Fetch successful: {}", response);
|
||||||
|
self.data = response;
|
||||||
|
}
|
||||||
|
|
||||||
|
Msg::FetchError => {
|
||||||
|
self.fetching = false;
|
||||||
|
error!("There was an error connecting to API");
|
||||||
|
self.data = String::from("401 Unauthorized");
|
||||||
|
}
|
||||||
|
|
||||||
|
Msg::FetchData() => {
|
||||||
|
self.fetching = true;
|
||||||
|
self.get_data();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&self) -> Html {
|
||||||
|
let onclick = self.component_link.callback(|_| Msg::FetchData());
|
||||||
|
|
||||||
|
html! {
|
||||||
|
<div class="uk-card uk-card-default uk-card-body uk-width-1-3@s uk-position-center">
|
||||||
|
<h1 class="uk-card-title">{ "Suojattu data" }</h1>
|
||||||
|
<p>{&self.data}</p>
|
||||||
|
<button
|
||||||
|
class="uk-button uk-button-primary",
|
||||||
|
type="button",
|
||||||
|
disabled=self.fetching,
|
||||||
|
onclick=onclick>
|
||||||
|
{ "Hae data" }
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,7 +6,7 @@ extern crate web_logger;
|
||||||
mod component;
|
mod component;
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
|
|
||||||
use component::login::LoginComponent;
|
use component::{login::LoginComponent, protected::ProtectedComponent};
|
||||||
use yew::prelude::*;
|
use yew::prelude::*;
|
||||||
use yew::virtual_dom::VNode;
|
use yew::virtual_dom::VNode;
|
||||||
use yew_router::{prelude::*, switch::Permissive, Switch};
|
use yew_router::{prelude::*, switch::Permissive, Switch};
|
||||||
|
@ -37,6 +37,10 @@ impl Component for App {
|
||||||
fn view(&self) -> VNode {
|
fn view(&self) -> VNode {
|
||||||
html! {
|
html! {
|
||||||
<div>
|
<div>
|
||||||
|
<nav class="menu",>
|
||||||
|
<RouterButton<AppRoute> route=AppRoute::Login> {"Kirjautuminen"} </RouterButton<AppRoute>>
|
||||||
|
<RouterButton<AppRoute> route=AppRoute::Root> {"Suojattu data"} </RouterButton<AppRoute>>
|
||||||
|
</nav>
|
||||||
<Router<AppRoute>
|
<Router<AppRoute>
|
||||||
render = Router::render(|switch: AppRoute| {
|
render = Router::render(|switch: AppRoute| {
|
||||||
match switch {
|
match switch {
|
||||||
|
@ -44,7 +48,7 @@ impl Component for App {
|
||||||
AppRoute::PageNotFound(Permissive(None)) => html!{"Page not found"},
|
AppRoute::PageNotFound(Permissive(None)) => html!{"Page not found"},
|
||||||
AppRoute::PageNotFound(Permissive(Some(missed_route))) => html!{format!("Page '{}' not found", missed_route)},
|
AppRoute::PageNotFound(Permissive(Some(missed_route))) => html!{format!("Page '{}' not found", missed_route)},
|
||||||
AppRoute::Root => {
|
AppRoute::Root => {
|
||||||
html!{"hello there!"}
|
html!{<ProtectedComponent />}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue