Skip to content

Commit

Permalink
feat(vaultwarden): working sync
Browse files Browse the repository at this point in the history
  • Loading branch information
NorkzYT committed May 24, 2024
1 parent 2699d49 commit b621b65
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 73 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<img src="docs/content/assets/img/bitwardensync-cover-rl.png" width="490">
</p>

<p align="center">Automatically Sync your Password Managers Data to Bitwarden</p>
<p align="center">Automatically Sync your Password Managers Data to Vaultwarden</p>
<div align="center">
<!-- Contributions Welcome Badge -->
<a href="CODE_OF_CONDUCT.md" target="_blank">
Expand Down
28 changes: 11 additions & 17 deletions docker/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,15 @@
# Logging start of script
echo "Starting Bitwarden setup and synchronization script."

echo "--------------------------------"

npx ts-node /backuponepass/vaultPurge.ts

echo "--------------------------------"

# Wait 1 second
sleep 1

# Connect the CLI to the Bitwarden server using an environment variable
echo "Setting Bitwarden server configuration to $BITWARDEN_SYNC_HOST."
bw config server "$BITWARDEN_SYNC_HOST"
Expand All @@ -25,21 +34,6 @@ session_key=$(bw unlock --raw --passwordenv BITWARDEN_SYNC_BW_PASSWORD)
echo ""
echo "Unlocked Bitwarden vault."

# Wait 1 second
sleep 1

echo "--------------------------------"

npx ts-node /backuponepass/vaultPurge.ts

# Sync Bitwarden data to CLI
echo ""
echo "Synchronizing Bitwarden data."
bw sync
echo ""

echo "--------------------------------"

# Find all .1pux files in the specified directory
pux_files=$(find /bitwardensync/data -type f -name "*.1pux" | head -n 1)

Expand All @@ -51,8 +45,8 @@ if [ -n "$pux_files" ]; then
bw import 1password1pux "$file" --session "$session_key"
# Remove the file after import to avoid re-importing
echo ""
echo "Removing $file after import."
rm -f "$file"
# echo "Removing $file after import."
# rm -f "$file"
done
else
echo "No .1pux files found."
Expand Down
122 changes: 81 additions & 41 deletions docker/vaultPurge.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import puppeteer from 'puppeteer';
import * as dotenv from 'dotenv';
import { authenticator } from 'otplib';
import puppeteer from "puppeteer";
import * as dotenv from "dotenv";
import { authenticator } from "otplib";
import { join } from "path";
import { writeFileSync } from "fs";

dotenv.config();

(async () => {
const browser = await puppeteer.launch({ headless: true });
const browser = await puppeteer.launch({
headless: true,
args: ["--no-sandbox"],
});
const page = await browser.newPage();
await page.setViewport({ width: 1920, height: 1080 });
const bitwardenUrl = process.env.BITWARDEN_SYNC_HOST as string;
Expand All @@ -16,100 +21,135 @@ dotenv.config();

if (!bitwardenUrl || !email || !password || !otpSecret) {
console.error(
'Host, email, password, or OTP secret environment variables are not set'
"Host, email, password, or OTP secret environment variables are not set"
);
process.exit(1);
}

try {
await page.goto(bitwardenUrl, { waitUntil: 'networkidle2' });
await page.goto(bitwardenUrl, {
waitUntil: "networkidle2",
timeout: 60000,
});

console.log("Page loaded.");

// Type in email
await page.click('input#email');
await page.click("input#login_input_email");
await page.keyboard.type(email, { delay: 10 });
console.log("Email entered.");

// Click the Continue button
await page.evaluate(() => {
const continueButton = Array.from(
document.querySelectorAll("button")
).find((button) => button.textContent?.includes("Continue"));
if (continueButton) {
(continueButton as HTMLElement).click();
}
});
console.log("Clicked Continue button after email.");

// Type in password
await page.click('input#masterPassword');
await page.click("input#login_input_master-password");
await page.keyboard.type(password, { delay: 10 });
console.log("Password entered.");

// Click Log In button
await page.click('button.btn-submit');
await page.click('button[type="submit"]');
console.log("Clicked Log In button.");

// Wait for navigation
await page.waitForNavigation({ waitUntil: 'networkidle2' });
await page.waitForNavigation({ waitUntil: "networkidle2", timeout: 60000 });
console.log("Navigation after Log In.");

// Handle potential 2FA step
const twoFactorInputSelector = 'input#code';
const twoFactorContinueSelector = 'button.btn-submit';
const twoFactorInputSelector = "input#code";

if (await page.$(twoFactorInputSelector)) {
console.log('Two-factor authentication step detected');
console.log("Two-factor authentication step detected");

// Generate OTP code
const otpCode = authenticator.generate(otpSecret);

await page.click(twoFactorInputSelector);
await page.keyboard.type(otpCode, { delay: 10 });
await page.click(twoFactorContinueSelector);
console.log("OTP code entered.");

// Wait for navigation
await page.waitForNavigation({ waitUntil: 'networkidle2' });
// Ensure the OTP input field loses focus
await page.keyboard.press("Tab");
await page.keyboard.press("Tab");
await page.keyboard.press("Enter");
console.log("Navigation after clicking Continue.");
}

// Click on Settings
await page.click('a[href="#/settings"]');

// Wait for the card-body containing the "Purge Vault" button
await page.waitForSelector('.card-body', { visible: true });

// Scroll all the way down
await page.evaluate(() => {
window.scrollTo(0, document.body.scrollHeight);
});
console.log("Logged in successfully.");

console.log('Scrolled to the bottom of the page.');
// Navigate to settings page directly
const settingsUrl = `${bitwardenUrl}/#/settings`;
await page.goto(settingsUrl, { waitUntil: "networkidle2", timeout: 60000 });
console.log("Navigated to settings page.");

// Wait for the "Purge Vault" button to be in the viewport and click it
await page.waitForSelector('button.btn-outline-danger', { visible: true });
await page.waitForSelector('button[type="button"]', {
visible: true,
timeout: 60000,
});
await page.evaluate(() => {
const purgeButton = Array.from(document.querySelectorAll('button')).find(
(button) => button.textContent?.includes('Purge Vault')
const purgeButton = Array.from(document.querySelectorAll("button")).find(
(button) => button.textContent?.includes("Purge vault")
);
if (purgeButton) {
purgeButton.scrollIntoView();
purgeButton.click();
(purgeButton as HTMLElement).click();
}
});

console.log('Clicked the "Purge Vault" button.');
console.log('Clicked the "Purge vault" button.');

// Wait for 2 seconds before typing the password
await new Promise((resolve) => setTimeout(resolve, 2000));

// Start typing the password directly
await page.keyboard.type(password, { delay: 10 });
console.log('Typed the master password for confirmation.');
console.log("Typed the master password for confirmation.");

// Click the final Purge Vault confirmation button
await page.waitForSelector(
'form .modal-footer .btn.btn-danger.btn-submit',
{ visible: true }
"form .modal-footer .btn.btn-danger.btn-submit",
{ visible: true, timeout: 60000 }
);
await page.evaluate(() => {
const confirmPurgeButton = document.querySelector(
'form .modal-footer .btn.btn-danger.btn-submit'
"form .modal-footer .btn.btn-danger.btn-submit"
);
if (confirmPurgeButton) {
(confirmPurgeButton as HTMLElement).click();
}
});

console.log('Clicked the final "Purge Vault" confirmation button.');
console.log('Clicked the final "Purge vault" confirmation button.');

// Wait for the purge to complete
await page.waitForNavigation({ waitUntil: 'networkidle2', timeout: 60000 });
await page.waitForNavigation({ waitUntil: "networkidle2", timeout: 60000 });
console.log("Purge completed successfully.");
} catch (error) {
console.error('An error occurred:', error);
console.error("An error occurred:", error);

// Take a screenshot if an error occurs
const screenshotPath = join("/bitwardensync/data", "error_screenshot.png");
await page.screenshot({ path: screenshotPath });
console.log(`Screenshot saved at ${screenshotPath}`);

// Save the error message to a file
const errorMessagePath = join("/bitwardensync/data", "error_message.txt");

// Check the type of error and handle accordingly
if (error instanceof Error) {
writeFileSync(errorMessagePath, error.message);
} else {
writeFileSync(errorMessagePath, "An unknown error occurred.");
}

console.log(`Error message saved at ${errorMessagePath}`);
} finally {
// Close the browser
await browser.close();
Expand Down
44 changes: 32 additions & 12 deletions dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,9 @@ FROM vaultwarden/server:latest

LABEL maintainer="NorkzYT richard@pcscorp.dev"

# Copy entrypoint.sh file
COPY docker/entrypoint.sh /backuponepass/entrypoint.sh

# Ensure the script contains execute permissions
RUN chmod +x /backuponepass/entrypoint.sh

# Install packages and clean up cache to reduce image size
RUN apt-get update && apt-get install -y \
# Install required packages including build-essential and Chromium
RUN apt-get update && \
apt-get install -y \
libssl-dev \
ca-certificates \
openssl \
Expand All @@ -18,12 +13,37 @@ RUN apt-get update && apt-get install -y \
rsync \
jq \
nodejs \
npm &&
apt-get clean &&
npm \
build-essential \
chromium \
fonts-ipafont-gothic \
fonts-wqy-zenhei \
fonts-thai-tlwg \
fonts-kacst \
fonts-freefont-ttf \
libxss1 \
--no-install-recommends && \
npm install -g @bitwarden/cli && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*

# Copy package.json and install npm packages
# Set environment variables for Puppeteer
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD true
ENV PUPPETEER_EXECUTABLE_PATH /usr/bin/chromium

# Copy necessary files
COPY docker/entrypoint.sh /backuponepass/entrypoint.sh
COPY docker/vaultPurge.ts /backuponepass/vaultPurge.ts
COPY package.json /backuponepass/package.json
RUN cd /backuponepass && npm install
COPY tsconfig.json /backuponepass/tsconfig.json

# Ensure the script contains execute permissions
RUN chmod +x /backuponepass/entrypoint.sh

# Set working directory
WORKDIR /backuponepass

# Install npm packages
RUN npm install

ENTRYPOINT ["/backuponepass/entrypoint.sh"]
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@
"node": ">=16.0.0"
},
"dependencies": {
"@bitwarden/cli": "^2024.4.1",
"dotenv": "^16.4.5",
"otplib": "^12.0.1",
"puppeteer": "^22.9.0"
"puppeteer": "^22.9.0",
"ts-node": "^10.9.2"
}
}

0 comments on commit b621b65

Please sign in to comment.