[Draft] Implement Reviewer Notice Link using Login Redirect #40
15 changed files with 50 additions and 57 deletions
18
Cargo.lock
generated
18
Cargo.lock
generated
|
|
@ -1351,7 +1351,6 @@ dependencies = [
|
||||||
"http",
|
"http",
|
||||||
"ldap3",
|
"ldap3",
|
||||||
"lettre",
|
"lettre",
|
||||||
"listenfd",
|
|
||||||
"log",
|
"log",
|
||||||
"mime_guess",
|
"mime_guess",
|
||||||
"multipart_helper",
|
"multipart_helper",
|
||||||
|
|
@ -1504,17 +1503,6 @@ version = "0.1.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4"
|
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]]
|
[[package]]
|
||||||
name = "local-channel"
|
name = "local-channel"
|
||||||
version = "0.1.3"
|
version = "0.1.3"
|
||||||
|
|
@ -2668,12 +2656,6 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "uuid"
|
|
||||||
version = "1.2.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "422ee0de9031b5b948b97a8fc04e3aa35230001a722ddd27943e0be31564ce4c"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "vcpkg"
|
name = "vcpkg"
|
||||||
version = "0.2.15"
|
version = "0.2.15"
|
||||||
|
|
|
||||||
|
|
@ -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
|
- missing lines around lists and headings triggering lints in changelog
|
||||||
- new clippy lints
|
- new clippy lints
|
||||||
|
- create storage dir when missing
|
||||||
|
|
||||||
### Change
|
### Change
|
||||||
|
|
||||||
|
|
@ -20,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
- startup errors are more descriptive
|
- startup errors are more descriptive
|
||||||
- the confirmation link now works after confirmation so that submissions can be deleted early by the submitter
|
- 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
|
- 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
|
### Removed
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,11 @@ RUN useradd -u $UID -g dev -m dev
|
||||||
|
|
||||||
RUN echo -e "\ndev ALL=(ALL:ALL) NOPASSWD: ALL" >> /etc/sudoers
|
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
|
RUN pacman -Syu --noconfirm
|
||||||
|
|
||||||
COPY . /src_dir
|
COPY . /src_dir
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,6 @@ lettre = { workspace = true, default-features = false, features = ["sendmail-tra
|
||||||
# use rustls a native tls library rather than openssl,
|
# use rustls a native tls library rather than openssl,
|
||||||
# as depending on c dependencies can get annoying even when vendoring
|
# as depending on c dependencies can get annoying even when vendoring
|
||||||
ldap3 = { workspace = true, default-features = false, features = ["tls-rustls"] }
|
ldap3 = { workspace = true, default-features = false, features = ["tls-rustls"] }
|
||||||
listenfd = { workspace = true }
|
|
||||||
log = { workspace = true }
|
log = { workspace = true }
|
||||||
mime_guess = { workspace = true }
|
mime_guess = { workspace = true }
|
||||||
multipart_helper = { workspace = true }
|
multipart_helper = { workspace = true }
|
||||||
|
|
|
||||||
BIN
packages/jobboerse/THIRDPARTY.toml
(Stored with Git LFS)
BIN
packages/jobboerse/THIRDPARTY.toml
(Stored with Git LFS)
Binary file not shown.
|
|
@ -1,11 +1,12 @@
|
||||||
use crate::job_offers::error::EmailError;
|
use crate::job_offers::error::EmailError;
|
||||||
|
use crate::route::RETURN_TO;
|
||||||
use crate::server_config::EmailConfig;
|
use crate::server_config::EmailConfig;
|
||||||
use crate::template;
|
use crate::template;
|
||||||
use handlebars::Handlebars;
|
use handlebars::Handlebars;
|
||||||
use lettre::message::header::{Header, HeaderName, HeaderValue, UserAgent};
|
use lettre::message::header::{Header, HeaderName, HeaderValue, UserAgent};
|
||||||
use lettre::message::{Mailbox, SinglePart};
|
use lettre::message::{Mailbox, SinglePart};
|
||||||
use lettre::{Address, AsyncTransport};
|
use lettre::{Address, AsyncTransport};
|
||||||
use log::warn;
|
use log::{warn, debug};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
|
|
@ -68,13 +69,20 @@ pub(crate) async fn send_confirmation_email(
|
||||||
pub(crate) async fn send_reviewer_notice(
|
pub(crate) async fn send_reviewer_notice(
|
||||||
hb: &Handlebars<'_>,
|
hb: &Handlebars<'_>,
|
||||||
email_config: &EmailConfig,
|
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
|
// 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(
|
let body = match hb.render(
|
||||||
template::EMAIL_REVIEWER_NOTICE,
|
template::EMAIL_REVIEWER_NOTICE,
|
||||||
&json!({ "highlight_link": url }),
|
&json!({ "highlight_link": login_url }),
|
||||||
) {
|
) {
|
||||||
Ok(body) => body,
|
Ok(body) => body,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
|
|
@ -83,6 +91,8 @@ pub(crate) async fn send_reviewer_notice(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
debug!("Sending:\n{body}");
|
||||||
|
|
||||||
let message = lettre::Message::builder()
|
let message = lettre::Message::builder()
|
||||||
.from(email_config.from.to_owned())
|
.from(email_config.from.to_owned())
|
||||||
.to(email_config.from.to_owned())
|
.to(email_config.from.to_owned())
|
||||||
|
|
|
||||||
|
|
@ -74,10 +74,6 @@ pub(crate) enum SeverInitializationError {
|
||||||
TemplateError(#[from] handlebars::TemplateError),
|
TemplateError(#[from] handlebars::TemplateError),
|
||||||
#[error("the jobboerse server encountered a fatal io error during startup, could not bind socket: {0}")]
|
#[error("the jobboerse server encountered a fatal io error during startup, could not bind socket: {0}")]
|
||||||
Bind(std::io::Error),
|
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}")]
|
#[error("the jobboerse server encountered a fatal io error during startup, could not start the server: {0}")]
|
||||||
Run(std::io::Error),
|
Run(std::io::Error),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -654,6 +654,10 @@ impl JobOffers {
|
||||||
let storage = &config.config.data_storage_path;
|
let storage = &config.config.data_storage_path;
|
||||||
info!("Loading Job Offers from {}", storage.display());
|
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)
|
let mut dir = tokio::fs::read_dir(storage)
|
||||||
.await
|
.await
|
||||||
.map_err(|err| JobofferLoadError::IO(err, storage.clone()))?;
|
.map_err(|err| JobofferLoadError::IO(err, storage.clone()))?;
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,6 @@ use actix_web::middleware::{ErrorHandlers, NormalizePath, TrailingSlash};
|
||||||
use actix_web::{get, web, App, HttpServer};
|
use actix_web::{get, web, App, HttpServer};
|
||||||
use error::SeverInitializationError;
|
use error::SeverInitializationError;
|
||||||
use handlebars::Handlebars;
|
use handlebars::Handlebars;
|
||||||
use listenfd::ListenFd;
|
|
||||||
use log::{error, LevelFilter, SetLoggerError};
|
use log::{error, LevelFilter, SetLoggerError};
|
||||||
// internal imports
|
// internal imports
|
||||||
use job_offers::lease::SubmissionLimiter;
|
use job_offers::lease::SubmissionLimiter;
|
||||||
|
|
@ -65,8 +64,6 @@ async fn main() -> Result<(), error::SeverInitializationError> {
|
||||||
async fn run() -> Result<(), error::SeverInitializationError> {
|
async fn run() -> Result<(), error::SeverInitializationError> {
|
||||||
let server_config = ServerConfig::load().await?;
|
let server_config = ServerConfig::load().await?;
|
||||||
|
|
||||||
let mut listen_fd = ListenFd::from_env();
|
|
||||||
|
|
||||||
let mut hb = Handlebars::new();
|
let mut hb = Handlebars::new();
|
||||||
let bundle =
|
let bundle =
|
||||||
bundle_licenses_lib::format::Format::Toml.deserialize_from_reader(route::LICENSE_BUNDLE)?;
|
bundle_licenses_lib::format::Format::Toml.deserialize_from_reader(route::LICENSE_BUNDLE)?;
|
||||||
|
|
@ -151,19 +148,12 @@ async fn run() -> Result<(), error::SeverInitializationError> {
|
||||||
app
|
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 ipv6 = SocketAddr::from((Ipv6Addr::UNSPECIFIED, port));
|
||||||
let ipv4 = SocketAddr::from((Ipv4Addr::UNSPECIFIED, port));
|
let ipv4 = SocketAddr::from((Ipv4Addr::UNSPECIFIED, port));
|
||||||
|
|
||||||
server
|
server
|
||||||
.bind([ipv6, ipv4].as_slice())
|
.bind([ipv6, ipv4].as_slice())
|
||||||
.map_err(SeverInitializationError::Bind)?
|
.map_err(SeverInitializationError::Bind)?
|
||||||
}
|
|
||||||
.run()
|
.run()
|
||||||
.await
|
.await
|
||||||
.map_err(SeverInitializationError::Run)?;
|
.map_err(SeverInitializationError::Run)?;
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ pub(crate) mod form_constants;
|
||||||
mod job_offer;
|
mod job_offer;
|
||||||
mod license;
|
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::{
|
pub(crate) use job_offer::{
|
||||||
confirmation::JOBOFFER_CONFIRM_ROUTE,
|
confirmation::JOBOFFER_CONFIRM_ROUTE,
|
||||||
create::JOBOFFER_CREATION_ROUTE,
|
create::JOBOFFER_CREATION_ROUTE,
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ pub(crate) fn configure(config: &mut ServiceConfig) {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) const LOGIN_ROUTE: &str = "login";
|
pub(crate) const LOGIN_ROUTE: &str = "login";
|
||||||
|
pub(crate) const RETURN_TO: &str = "return_to";
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub(crate) struct LoginQuery {
|
pub(crate) struct LoginQuery {
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,8 @@ use multipart_helper::MultipartFieldError;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
|
use super::auth::RETURN_TO;
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
#[error("Some error occurred while attempting to display an error page")]
|
#[error("Some error occurred while attempting to display an error page")]
|
||||||
pub struct ErrorHandlerResponseError;
|
pub struct ErrorHandlerResponseError;
|
||||||
|
|
@ -226,7 +228,7 @@ fn login_url_with_return(req: &HttpRequest, return_to: &str) -> Result<Url, UrlG
|
||||||
|
|
||||||
login_url
|
login_url
|
||||||
.query_pairs_mut()
|
.query_pairs_mut()
|
||||||
.append_pair("return_to", return_to);
|
.append_pair(RETURN_TO, return_to);
|
||||||
|
|
||||||
Ok(login_url)
|
Ok(login_url)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ use crate::job_offers::{
|
||||||
use crate::route::form_constants::{self, UploadLimits};
|
use crate::route::form_constants::{self, UploadLimits};
|
||||||
use crate::route::job_offer::confirmation::JOBOFFER_CONFIRM_ROUTE;
|
use crate::route::job_offer::confirmation::JOBOFFER_CONFIRM_ROUTE;
|
||||||
use crate::route::job_offer::error::{FormProcessingError, SubmissionResponseError};
|
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::server_config::ServerConfig;
|
||||||
use crate::util::{parse_date, parse_datetime, process_links, process_new_attachments};
|
use crate::util::{parse_date, parse_datetime, process_links, process_new_attachments};
|
||||||
use crate::{email, template};
|
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 let Some(email_config) = &config.config.email {
|
||||||
if !is_pre_approved {
|
if !is_pre_approved {
|
||||||
match created_offer.highlight_link(req) {
|
match created_offer
|
||||||
Ok(highlight_url) => {
|
.highlight_link(req)
|
||||||
email::send_reviewer_notice(hb, email_config, highlight_url).await;
|
.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) => {
|
Err(err) => {
|
||||||
error!("Failed to generate highlight link: {}", err)
|
error!("Failed to generate highlight link: {}", err)
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
Automatischer Hinweis: Eine neue Stellenausschreibung wurde zur Jobbörse eingereicht!
|
Automatischer Hinweis: Eine neue Stellenausschreibung wurde zur Jobbörse eingereicht!
|
||||||
|
|
||||||
{{highlight_link}}
|
{{{highlight_link}}}
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,9 @@ RUN_DIR="${DIR}/../packages/jobboerse"
|
||||||
|
|
||||||
pushd "${RUN_DIR}"
|
pushd "${RUN_DIR}"
|
||||||
|
|
||||||
systemfd \
|
export RUST_LOG=debug
|
||||||
--no-pid \
|
|
||||||
-s http::8080 \
|
cargo watch \
|
||||||
-- cargo watch \
|
|
||||||
-i ./packages/jobboerse/config/dev-config.toml \
|
-i ./packages/jobboerse/config/dev-config.toml \
|
||||||
-x "run --package jobboerse --bin jobboerse --release --features=dev_mode -- --config=\"${CFG}\" --mode=development"
|
-x "run --package jobboerse --bin jobboerse --release --features=dev_mode -- --config=\"${CFG}\" --mode=development"
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue