[Draft] Implement Reviewer Notice Link using Login Redirect #40

Merged
ben merged 5 commits from ben/Jobboerse:notice-link-via-login into main 2023-01-28 18:00:15 +01:00
15 changed files with 50 additions and 57 deletions

18
Cargo.lock generated
View file

@ -1351,7 +1351,6 @@ dependencies = [
"http",
"ldap3",
"lettre",
"listenfd",
"log",
"mime_guess",
"multipart_helper",
@ -1504,17 +1503,6 @@ version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4"
[[package]]
name = "listenfd"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14e4fcc00ff6731d94b70e16e71f43bda62883461f31230742e3bc6dddf12988"
dependencies = [
"libc",
"uuid",
"winapi",
]
[[package]]
name = "local-channel"
version = "0.1.3"
@ -2668,12 +2656,6 @@ dependencies = [
"serde",
]
[[package]]
name = "uuid"
version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "422ee0de9031b5b948b97a8fc04e3aa35230001a722ddd27943e0be31564ce4c"
[[package]]
name = "vcpkg"
version = "0.2.15"

View file

@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- missing lines around lists and headings triggering lints in changelog
- new clippy lints
- create storage dir when missing
### Change
@ -20,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- startup errors are more descriptive
- the confirmation link now works after confirmation so that submissions can be deleted early by the submitter
- this changes the on-disk format slightly as the token now has a different scope and was moved accordingly
- reviewe notice link is now indirect via the login page
### Removed

View file

@ -12,6 +12,11 @@ RUN useradd -u $UID -g dev -m dev
RUN echo -e "\ndev ALL=(ALL:ALL) NOPASSWD: ALL" >> /etc/sudoers
RUN rm -rd /etc/pacman.d/gnupg
RUN pacman-key --init
RUN pacman-key --populate
RUN pacman -Sy archlinux-keyring --noconfirm
RUN pacman -Syu --noconfirm
COPY . /src_dir

View file

@ -35,7 +35,6 @@ lettre = { workspace = true, default-features = false, features = ["sendmail-tra
# use rustls a native tls library rather than openssl,
# as depending on c dependencies can get annoying even when vendoring
ldap3 = { workspace = true, default-features = false, features = ["tls-rustls"] }
listenfd = { workspace = true }
log = { workspace = true }
mime_guess = { workspace = true }
multipart_helper = { workspace = true }

BIN
packages/jobboerse/THIRDPARTY.toml (Stored with Git LFS)

Binary file not shown.

View file

@ -1,11 +1,12 @@
use crate::job_offers::error::EmailError;
use crate::route::RETURN_TO;
use crate::server_config::EmailConfig;
use crate::template;
use handlebars::Handlebars;
use lettre::message::header::{Header, HeaderName, HeaderValue, UserAgent};
use lettre::message::{Mailbox, SinglePart};
use lettre::{Address, AsyncTransport};
use log::warn;
use log::{warn, debug};
use serde_json::json;
use url::Url;
@ -68,13 +69,20 @@ pub(crate) async fn send_confirmation_email(
pub(crate) async fn send_reviewer_notice(
hb: &Handlebars<'_>,
email_config: &EmailConfig,
url: Url,
highlight_url: Url,
mut login_url: Url,
) {
// successfully send a confirmation e-mail now send a notice to our-self
login_url
.query_pairs_mut()
.append_pair(RETURN_TO, highlight_url.as_str());
debug!("Url:\n{login_url}");
let body = match hb.render(
template::EMAIL_REVIEWER_NOTICE,
&json!({ "highlight_link": url }),
&json!({ "highlight_link": login_url }),
) {
Ok(body) => body,
Err(err) => {
@ -83,6 +91,8 @@ pub(crate) async fn send_reviewer_notice(
}
};
debug!("Sending:\n{body}");
let message = lettre::Message::builder()
.from(email_config.from.to_owned())
.to(email_config.from.to_owned())

View file

@ -74,10 +74,6 @@ pub(crate) enum SeverInitializationError {
TemplateError(#[from] handlebars::TemplateError),
#[error("the jobboerse server encountered a fatal io error during startup, could not bind socket: {0}")]
Bind(std::io::Error),
#[error("the jobboerse server encountered a fatal io error during startup, could not listen on socket: {0}")]
TakeListen(std::io::Error),
#[error("the jobboerse server encountered a fatal io error during startup, could not take listen socket: {0}")]
Listen(std::io::Error),
#[error("the jobboerse server encountered a fatal io error during startup, could not start the server: {0}")]
Run(std::io::Error),
}

View file

@ -654,6 +654,10 @@ impl JobOffers {
let storage = &config.config.data_storage_path;
info!("Loading Job Offers from {}", storage.display());
tokio::fs::create_dir_all(storage)
.await
.map_err(|err| JobofferLoadError::IO(err, storage.clone()))?;
let mut dir = tokio::fs::read_dir(storage)
.await
.map_err(|err| JobofferLoadError::IO(err, storage.clone()))?;

View file

@ -16,7 +16,6 @@ use actix_web::middleware::{ErrorHandlers, NormalizePath, TrailingSlash};
use actix_web::{get, web, App, HttpServer};
use error::SeverInitializationError;
use handlebars::Handlebars;
use listenfd::ListenFd;
use log::{error, LevelFilter, SetLoggerError};
// internal imports
use job_offers::lease::SubmissionLimiter;
@ -65,8 +64,6 @@ async fn main() -> Result<(), error::SeverInitializationError> {
async fn run() -> Result<(), error::SeverInitializationError> {
let server_config = ServerConfig::load().await?;
let mut listen_fd = ListenFd::from_env();
let mut hb = Handlebars::new();
let bundle =
bundle_licenses_lib::format::Format::Toml.deserialize_from_reader(route::LICENSE_BUNDLE)?;
@ -151,19 +148,12 @@ async fn run() -> Result<(), error::SeverInitializationError> {
app
});
if let Some(l) = listen_fd
.take_tcp_listener(0)
.map_err(SeverInitializationError::TakeListen)?
{
server.listen(l).map_err(SeverInitializationError::Listen)?
} else {
let ipv6 = SocketAddr::from((Ipv6Addr::UNSPECIFIED, port));
let ipv4 = SocketAddr::from((Ipv4Addr::UNSPECIFIED, port));
let ipv6 = SocketAddr::from((Ipv6Addr::UNSPECIFIED, port));
let ipv4 = SocketAddr::from((Ipv4Addr::UNSPECIFIED, port));
server
.bind([ipv6, ipv4].as_slice())
.map_err(SeverInitializationError::Bind)?
}
server
.bind([ipv6, ipv4].as_slice())
.map_err(SeverInitializationError::Bind)?
.run()
.await
.map_err(SeverInitializationError::Run)?;

View file

@ -15,7 +15,7 @@ pub(crate) mod form_constants;
mod job_offer;
mod license;
pub(crate) use auth::{LOGIN_ROUTE, LOGOUT_ROUTE};
pub(crate) use auth::{LOGIN_ROUTE, LOGOUT_ROUTE, RETURN_TO};
pub(crate) use job_offer::{
confirmation::JOBOFFER_CONFIRM_ROUTE,
create::JOBOFFER_CREATION_ROUTE,

View file

@ -24,6 +24,7 @@ pub(crate) fn configure(config: &mut ServiceConfig) {
}
pub(crate) const LOGIN_ROUTE: &str = "login";
pub(crate) const RETURN_TO: &str = "return_to";
#[derive(Serialize, Deserialize)]
pub(crate) struct LoginQuery {

View file

@ -24,6 +24,8 @@ use multipart_helper::MultipartFieldError;
use serde_json::json;
use url::Url;
use super::auth::RETURN_TO;
#[derive(Debug, thiserror::Error)]
#[error("Some error occurred while attempting to display an error page")]
pub struct ErrorHandlerResponseError;
@ -226,7 +228,7 @@ fn login_url_with_return(req: &HttpRequest, return_to: &str) -> Result<Url, UrlG
login_url
.query_pairs_mut()
.append_pair("return_to", return_to);
.append_pair(RETURN_TO, return_to);
Ok(login_url)
}

View file

@ -26,7 +26,7 @@ use crate::job_offers::{
use crate::route::form_constants::{self, UploadLimits};
use crate::route::job_offer::confirmation::JOBOFFER_CONFIRM_ROUTE;
use crate::route::job_offer::error::{FormProcessingError, SubmissionResponseError};
use crate::route::HTML_CONTENT;
use crate::route::{HTML_CONTENT, LOGIN_ROUTE};
use crate::server_config::ServerConfig;
use crate::util::{parse_date, parse_datetime, process_links, process_new_attachments};
use crate::{email, template};
@ -248,9 +248,12 @@ pub(crate) async fn create_job_offer<'data, 'config>(
if let Some(email_config) = &config.config.email {
if !is_pre_approved {
match created_offer.highlight_link(req) {
Ok(highlight_url) => {
email::send_reviewer_notice(hb, email_config, highlight_url).await;
match created_offer
.highlight_link(req)
.and_then(|highlight| Ok((highlight, req.url_for_static(LOGIN_ROUTE)?)))
{
Ok((highlight_url, login_url)) => {
email::send_reviewer_notice(hb, &email_config, highlight_url, login_url).await;
}
Err(err) => {
error!("Failed to generate highlight link: {}", err)

View file

@ -1,3 +1,3 @@
Automatischer Hinweis: Eine neue Stellenausschreibung wurde zur Jobbörse eingereicht!
{{highlight_link}}
{{{highlight_link}}}

View file

@ -8,11 +8,10 @@ RUN_DIR="${DIR}/../packages/jobboerse"
pushd "${RUN_DIR}"
systemfd \
--no-pid \
-s http::8080 \
-- cargo watch \
-i ./packages/jobboerse/config/dev-config.toml \
-x "run --package jobboerse --bin jobboerse --release --features=dev_mode -- --config=\"${CFG}\" --mode=development"
export RUST_LOG=debug
cargo watch \
-i ./packages/jobboerse/config/dev-config.toml \
-x "run --package jobboerse --bin jobboerse --release --features=dev_mode -- --config=\"${CFG}\" --mode=development"
popd