feat: initial working pwd reset method
This commit is contained in:
commit
c8856ddeb1
8 changed files with 3246 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
/target
|
||||||
|
.idea/
|
||||||
3086
Cargo.lock
generated
Normal file
3086
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
12
Cargo.toml
Normal file
12
Cargo.toml
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
[package]
|
||||||
|
name = "self-service-access-recovery"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
reqwest = { version = "0.13.1", features = ["json"] }
|
||||||
|
toml = "0.9.11"
|
||||||
|
serde = { version = "1.0.228", features = ["derive"] }
|
||||||
|
lettre = "0.11.19"
|
||||||
|
rocket = { version = "0.5.1", features = ["json"] }
|
||||||
|
serde_json = "1.0.149"
|
||||||
4
README.md
Normal file
4
README.md
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
# Phylax - credentials reset service for Purelymail
|
||||||
|
Phylax — from Greek φύλαξ, “guardian.” (Pronounced roughly FEE-lax.)
|
||||||
|
|
||||||
|
This is a backend service written in Rust for resetting a user's password in Purelymail.
|
||||||
0
src/auth.rs
Normal file
0
src/auth.rs
Normal file
45
src/main.rs
Normal file
45
src/main.rs
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
mod auth;
|
||||||
|
mod provider;
|
||||||
|
mod request_types;
|
||||||
|
|
||||||
|
#[macro_use] extern crate rocket;
|
||||||
|
|
||||||
|
use std::env;
|
||||||
|
use rocket::serde::json::Json;
|
||||||
|
use rocket::{Error, State};
|
||||||
|
use rocket::http::Status;
|
||||||
|
use crate::provider::{PurelymailProvider, SMTPUserCredentials};
|
||||||
|
use crate::request_types::PasswordResetRequest;
|
||||||
|
|
||||||
|
#[get("/")]
|
||||||
|
fn index() -> &'static str {
|
||||||
|
"Hello, world!"
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/onboard/<token>")]
|
||||||
|
fn onboard(token: String) -> String {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/doResetPassword", data = "<data>")]
|
||||||
|
async fn user(data: Json<PasswordResetRequest>, provider: &State<PurelymailProvider>) -> Result<String, Status> {
|
||||||
|
provider.reset_password(&data.email, &data.new_password).await
|
||||||
|
.map(|_| { String::from("Good") })
|
||||||
|
.or(Err(Status::BadRequest))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[launch]
|
||||||
|
fn rocket() -> _ {
|
||||||
|
let token = env::var("API_TOKEN").expect("API_TOKEN env var not set");
|
||||||
|
let smtp_webmaster_username = env::var("WEBMASTER_USERNAME")
|
||||||
|
.expect("WEBMASTER_USERNAME env var not set");
|
||||||
|
let smtp_webmaster_password = env::var("WEBMASTER_PASSWORD")
|
||||||
|
.expect("WEBMASTER_PASSWORD env var not set");
|
||||||
|
let provider: PurelymailProvider = PurelymailProvider::new(
|
||||||
|
token,
|
||||||
|
SMTPUserCredentials::new(&smtp_webmaster_username, &smtp_webmaster_password)
|
||||||
|
);
|
||||||
|
rocket::build()
|
||||||
|
.manage(provider)
|
||||||
|
.mount("/", routes![index, onboard, user])
|
||||||
|
}
|
||||||
90
src/provider.rs
Normal file
90
src/provider.rs
Normal file
|
|
@ -0,0 +1,90 @@
|
||||||
|
use reqwest::{Body, Client, RequestBuilder};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
pub struct SMTPUserCredentials {
|
||||||
|
username: String,
|
||||||
|
password: String,
|
||||||
|
}
|
||||||
|
impl SMTPUserCredentials {
|
||||||
|
pub fn new(username: &str, password: &str) -> SMTPUserCredentials {
|
||||||
|
SMTPUserCredentials {
|
||||||
|
username: username.to_string(),
|
||||||
|
password: password.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PurelymailProvider {
|
||||||
|
token: String,
|
||||||
|
webmaster: SMTPUserCredentials
|
||||||
|
}
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct UserListResponse {
|
||||||
|
r#type: String,
|
||||||
|
result: UserList
|
||||||
|
}
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct UserList {
|
||||||
|
users: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct UserUpdateRequest {
|
||||||
|
pub user_name: String,
|
||||||
|
pub new_user_name: Option<String>,
|
||||||
|
pub new_password: String,
|
||||||
|
pub enable_search_indexing: bool,
|
||||||
|
pub enable_password_reset: bool,
|
||||||
|
pub require_two_factor_authentication: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<UserUpdateRequest> for Body {
|
||||||
|
fn from(user: UserUpdateRequest) -> Body {
|
||||||
|
let bytes = serde_json::to_vec(&user).expect("Failed to serialize UserUpdateRequest");
|
||||||
|
Body::from(bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PurelymailProvider {
|
||||||
|
pub fn new(token: String, webmaster_cred: SMTPUserCredentials) -> PurelymailProvider {
|
||||||
|
PurelymailProvider { token, webmaster: webmaster_cred }
|
||||||
|
}
|
||||||
|
fn dispatch_onboarding_email() {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn with_authorize(&self, req: RequestBuilder) -> RequestBuilder {
|
||||||
|
req.header("Purelymail-Api-Token", &self.token)
|
||||||
|
}
|
||||||
|
pub async fn reset_password(&self, email_addr: &str, new_password: &str) -> Result<(), &str> {
|
||||||
|
println!("Resetting password");
|
||||||
|
let client = Client::new();
|
||||||
|
let r = self.with_authorize(
|
||||||
|
client.post("https://purelymail.com/api/v0/listUser")
|
||||||
|
).body("{}").send().await.expect("Failed to reset password");
|
||||||
|
let res = r.json::<UserListResponse>()
|
||||||
|
.await.expect("TODO: user list bad");
|
||||||
|
if res.r#type != "success" {
|
||||||
|
return Err("The API returned an error");
|
||||||
|
}
|
||||||
|
let users = res.result.users;
|
||||||
|
if !users.contains(&String::from(email_addr)) {
|
||||||
|
return Err("The user does not exist");
|
||||||
|
}
|
||||||
|
let r = self.with_authorize(
|
||||||
|
client.post("https://purelymail.com/api/v0/modifyUser")
|
||||||
|
).body(UserUpdateRequest {
|
||||||
|
user_name: email_addr.to_string(),
|
||||||
|
new_user_name: None,
|
||||||
|
new_password: new_password.to_string(),
|
||||||
|
enable_search_indexing: false,
|
||||||
|
enable_password_reset: false,
|
||||||
|
require_two_factor_authentication: false,
|
||||||
|
}).send().await;
|
||||||
|
if r.is_err() {
|
||||||
|
return Err("The API returned an error");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
7
src/request_types.rs
Normal file
7
src/request_types.rs
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct PasswordResetRequest {
|
||||||
|
pub email: String,
|
||||||
|
pub new_password: String,
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue