Skip to content

Commit

Permalink
Added support for due date
Browse files Browse the repository at this point in the history
  • Loading branch information
bhatti committed Dec 2, 2023
1 parent 2b4bad8 commit 7e04c08
Show file tree
Hide file tree
Showing 7 changed files with 268 additions and 33 deletions.
49 changes: 43 additions & 6 deletions assets/javascript/plexpass.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ async function viewAccount(id) {
}
const riskImage = account.risk_image ? `<img width="32" height="32" src="${account.risk_image}">` : '';

let expiration = buildExpiration(account.expired, account.expires_at);
let due = buildExpiration(account.overdue, account.due_at);
modalBody.innerHTML = `
<table class="table table-striped-columns">
<tr>
Expand Down Expand Up @@ -80,6 +82,9 @@ async function viewAccount(id) {
<button class="btn btn-outline-warning" onclick="copyToClipboard('${account.password}')">Copy</button>
</td>
</tr>
<tr>
<td><strong>Expires At:</strong></td><td>${expiration}</td>
</tr>
<tr>
<td><strong>URL:</strong></td><td> <span id="viewUrl">${account.website_url || ''}</span></td>
</tr>
Expand Down Expand Up @@ -125,6 +130,9 @@ async function viewAccount(id) {
<div id="collapseNotesInfo" class="accordion-collapse collapse" aria-labelledby="headingNotesInfo" data-bs-parent="#accountDetailsAccordion">
<div class="accordion-body">
<table class="table table-striped-columns">
<tr>
<td><strong>Due At:</strong></td><td>${due}</td>
</tr>
<tr>
<td><strong>Notes:</strong></td><td> <span id="viewNotes">${account.notes || ''}</span></td>
</tr>
Expand Down Expand Up @@ -171,6 +179,26 @@ async function viewAccount(id) {
await viewModal.show();
}

function buildExpiration(expired, expires_at) {
if (expired) {
return `<div class="alert alert-danger d-flex align-items-center" role="alert">
<svg class="bi flex-shrink-0 me-2" width="24" height="24" role="img" aria-label="Danger:"><use xlink:href="#exclamation-triangle-fill"/></svg>
<div>
${expires_at}
</div>
</div>`;
} else if (expires_at) {
return `<div class="alert alert-primary d-flex align-items-center" role="alert">
<svg class="bi flex-shrink-0 me-2" width="24" height="24" role="img" aria-label="Info:"><use xlink:href="#info-fill"/></svg>
<div>
${expires_at}
</div>
</div>`;
} else {
return '';
}
}

function buildOtpSection(otp, generatedOtp) {
if (!otp) {
return '';
Expand Down Expand Up @@ -278,13 +306,13 @@ async function editAccount(id) {
await showAccountForm(account);
}

async function addAccount(vault_id) {
async function addAccount(vault_id, kind) {
document.getElementById('editAccountTitle').innerText = 'Add Account';
const account = {
account_id: '',
vault_id: vault_id,
version: 0,
kind: 'Login',
kind: kind || 'Logins',
label: '',
description: '',
favorite: false,
Expand Down Expand Up @@ -330,14 +358,14 @@ function clearClipboardAfterDelay(text, delay) {
}
}


async function showAccountForm(account) {
const favorite = account.favorite ? 'checked' : '';
const modalBody = document.querySelector('#editAccountModal .modal-body');
let category_opts = '';
for (let i = 0; i < document.allCategories.length; i++) {
const next = document.allCategories[i];
const selected = account.category === next ? 'selected' : '';
const selected = account.category ? (account.category === next ? 'selected' : '') :
(account.kind === next ? 'selected' : '');
category_opts += `<option value="${next}" ${selected}>${next}</option>\n`;
}
modalBody.innerHTML = `
Expand Down Expand Up @@ -393,6 +421,11 @@ async function showAccountForm(account) {
<label for="website_url" class="form-label">Website URL:</label>
<input type="url" class="form-control" id="website_url" name="website_url" value="${account.website_url || ''}">
</div>
<div class="mb-3">
<label for="expiresAt" class="form-label">Expires At:</label>
<input type="date" class="form-control" id="expiresAt" name="expires_at" value="${account.expires_at || ''}" pattern="\\d{4}-\\d{2}-\\d{2}">
<div class="form-text">Please select password expiration date.</div>
</div>
<div class="form-group mb-3">
<label>OTP Secret (Base32):</label>
<input type="text" class="form-control" name="otp" value="${account.otp || ''}" placeholder="Base32 TOTP secret">
Expand Down Expand Up @@ -440,9 +473,14 @@ async function showAccountForm(account) {
</h2>
<div id="collapseNotes" class="accordion-collapse collapse" aria-labelledby="headingNotes" data-bs-parent="#accordionAccount">
<div class="accordion-body">
<div class="mb-3">
<label for="dueAt" class="form-label">Due At:</label>
<input type="date" class="form-control" id="dueAt" name="due_at" value="${account.due_at || ''}" pattern="\\d{4}-\\d{2}-\\d{2}">
<div class="form-text">Please select due date.</div>
</div>
<div class="form-group mb-3">
<label for="notes" class="form-label">Notes:</label>
<textarea class="form-control" id="notes" name="notes">${account.notes}</textarea>
<textarea class="form-control" id="notes" name="notes" rows="5">${account.notes || ''}</textarea>
</div>
<!-- Custom Fields for Edit -->
<h5>Custom Fields:</h5>
Expand Down Expand Up @@ -1263,7 +1301,6 @@ async function registerMFAKey() {
let response = await fetch('/ui/webauthn/register_start');
let options = await response.json();

console.log(JSON.stringify(options));
// Convert challenge from Base64URL to Base64, then to Uint8Array
const challengeBase64 = base64UrlToBase64(options.publicKey.challenge);
options.publicKey.challenge = Uint8Array.from(atob(challengeBase64), c => c.charCodeAt(0));
Expand Down
89 changes: 82 additions & 7 deletions src/controller/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,9 @@ pub struct CreateAccountRequest {
pub renew_interval_days: Option<i32>,
// expiration
pub expires_at: Option<NaiveDateTime>,
// due
pub due_at: Option<NaiveDateTime>,
// 2023-12-02T05:11:50.543995
// The custom fields of the account.
pub custom_name: Option<Vec<String>>,
// The custom fields of the account.
Expand Down Expand Up @@ -344,6 +347,7 @@ impl CreateAccountRequest {
notes: None,
renew_interval_days: None,
expires_at: None,
due_at: None,
custom_name: None,
custom_value: None,
password_min_uppercase: None,
Expand Down Expand Up @@ -380,6 +384,7 @@ impl CreateAccountRequest {
account.details.icon = self.icon.clone();
account.details.renew_interval_days = self.renew_interval_days;
account.details.expires_at = self.expires_at;
account.details.due_at = self.due_at;

account.credentials.password = self.password.clone();
account.credentials.form_fields = self.form_fields.clone().unwrap_or_default();
Expand Down Expand Up @@ -524,11 +529,16 @@ pub struct AccountResponse {
// renew interval
pub renew_interval_days: Option<i32>,
// expiration
pub expires_at: Option<NaiveDateTime>,
pub expires_at: Option<String>,
pub expired: bool,
// due
pub due_at: Option<String>,
pub overdue: bool,
// 2023-12-02T05:11:50.543995
// The metadata for dates of the account.
pub credentials_updated_at: Option<NaiveDateTime>,
pub credentials_updated_at: Option<String>,
// The metadata for date when password was analyzed.
pub analyzed_at: Option<NaiveDateTime>,
pub analyzed_at: Option<String>,
// minimum number of upper_case letters should be included.
pub password_min_uppercase: Option<usize>,
// minimum number of lower_case letters should be included.
Expand Down Expand Up @@ -583,12 +593,27 @@ impl AccountResponse {
password_min_length: Some(account.credentials.password_policy.min_length),
password_max_length: Some(account.credentials.password_policy.max_length),
renew_interval_days: account.details.renew_interval_days,
expires_at: account.details.expires_at,
credentials_updated_at: account.details.credentials_updated_at,
analyzed_at: account.details.analyzed_at,
expires_at: None,
expired: account.details.is_expired(),
due_at: None,
overdue: account.details.is_due(),
credentials_updated_at: None,
analyzed_at: None,
created_at: account.created_at,
updated_at: account.updated_at,
};
if let Some(expires_at) = &account.details.expires_at {
res.expires_at = Some(expires_at.format("%Y-%m-%d").to_string())
}
if let Some(due_at) = &account.details.due_at {
res.due_at = Some(due_at.format("%Y-%m-%d").to_string())
}
if let Some(credentials_updated_at) = &account.details.credentials_updated_at {
res.credentials_updated_at = Some(credentials_updated_at.format("%Y-%m-%d").to_string())
}
if let Some(analyzed_at) = &account.details.analyzed_at {
res.analyzed_at = Some(analyzed_at.format("%Y-%m-%d").to_string())
}
if let Some(ref otp) = res.otp {
res.generated_otp = Option::from(TOTP::new(otp).generate(30, Utc::now().timestamp() as u64));
}
Expand Down Expand Up @@ -685,7 +710,9 @@ pub struct UpdateAccountRequest {
// renew interval
pub renew_interval_days: Option<i32>,
// expiration
pub expires_at: Option<NaiveDateTime>,
pub expires_at: Option<NaiveDateTime>, // 2023-12-02T05:11:50.543995
// due
pub due_at: Option<NaiveDateTime>, // 2023-12-02T05:11:50.543995

// minimum number of upper_case letters should be included.
pub password_min_uppercase: Option<usize>,
Expand Down Expand Up @@ -728,6 +755,7 @@ impl UpdateAccountRequest {
custom_value: None,
renew_interval_days: None,
expires_at: None,
due_at: None,
password_min_uppercase: None,
password_min_lowercase: None,
password_min_digits: None,
Expand Down Expand Up @@ -769,6 +797,7 @@ impl UpdateAccountRequest {
account.credentials.otp = self.otp.clone();
account.details.renew_interval_days = self.renew_interval_days;
account.details.expires_at = self.expires_at;
account.details.due_at = self.due_at;

let mut password_policy = PasswordPolicy::new();

Expand Down Expand Up @@ -869,6 +898,7 @@ impl Account {
"icon" => account.details.icon = Some(value),
"renew_interval_days" => account.details.renew_interval_days = Some(value.parse::<i32>().unwrap_or(0)),
"expires_at" => account.details.expires_at = safe_parse_string_date(Option::from(value)),
"due_at" => account.details.due_at= safe_parse_string_date(Option::from(value)),
"custom_name" => custom_names.push(value),
"custom_value" => custom_values.push(value),
_ => {}
Expand Down Expand Up @@ -1058,3 +1088,48 @@ pub struct QueryAccountParams {
pub q: Option<String>,
}

#[cfg(test)]
mod tests {
use chrono::Utc;
use crate::controller::models::{AccountResponse, UpdateAccountRequest};
use crate::domain::models::{Account, AccountKind};

#[test]
fn test_should_serialize_account() {
let mut account = Account::new("vault0", AccountKind::Logins);
account.details.label = Some("my label".into());
account.details.version = 10;
account.details.favorite = true;
account.details.username = Some("test1".into());
account.credentials.password = Some("pass".into());
account.credentials.notes = Some("my notes".into());
account.details.email = Some("[email protected]".into());
account.details.website_url = Some("https://mail.cc".into());
account.details.category = Some("Contacts".into());
account.details.tags = vec!["Personal".to_string()];
account.details.renew_interval_days = Some(3);
account.details.expires_at = Some(Utc::now().naive_utc());
account.details.due_at = Some(Utc::now().naive_utc());
let account_response = AccountResponse::new(&account);
let account_json = serde_json::to_string(&account_response).unwrap();
let des_update_account: UpdateAccountRequest = serde_json::from_str(&account_json).unwrap();
let des_account = des_update_account.to_account();
assert_eq!(account.vault_id, des_account.vault_id);
assert_eq!(account.details.account_id, des_account.details.account_id);
assert_eq!(account.details.version, des_account.details.version);
assert_eq!(account.details.label, des_account.details.label);
assert_eq!(account.details.kind, des_account.details.kind);
assert_eq!(account.details.favorite, des_account.details.favorite);
assert_eq!(account.details.risk, des_account.details.risk);
assert_eq!(account.details.username, des_account.details.username);
assert_eq!(account.details.email, des_account.details.email);
assert_eq!(account.details.category, des_account.details.category);
assert_eq!(account.details.website_url, des_account.details.website_url);
assert_eq!(account.credentials.password, des_account.credentials.password);
assert_eq!(account.credentials.notes, des_account.credentials.notes);
assert_eq!(account.details.tags, des_account.details.tags);
assert_eq!(account.details.renew_interval_days, des_account.details.renew_interval_days);
assert_eq!(account.details.expires_at, des_account.details.expires_at);
assert_eq!(account.details.due_at , des_account.details.due_at);
}
}
Loading

0 comments on commit 7e04c08

Please sign in to comment.