Part 6 of 7 · Visa Manager App

Remembering Passwords, and the Duty That Comes With It

The cockpit’s last request was the obvious one, remember all the passwords, WordPress, Tawk.to, webmail, and fill them in whenever a login screen appears. It is also the most dangerous feature in the app if done lazily, credentials in a plain text file or hardcoded in source are a breach waiting for its day. This post is the password manager as built, encryption at rest, filling through the page, and the honest boundaries of the design.

The storage rule is absolute, credentials touch disk only encrypted. The app derives its encryption from a master password the user sets on first run, and stores each service’s credentials as ciphertext:

import json, os, base64, hashlib
from cryptography.fernet import Fernet

class PasswordManager:
    def __init__(self, master_password):
        digest = hashlib.sha256(master_password.encode()).digest()
        self.fernet = Fernet(base64.urlsafe_b64encode(digest))
        self.path = os.path.join(os.path.expanduser('~'), '.visa_manager', 'vault.bin')

    def save(self, service, username, password):
        vault = self.load_all()
        vault[service] = {'u': username, 'p': password}
        blob = self.fernet.encrypt(json.dumps(vault).encode())
        with open(self.path, 'wb') as f:
            f.write(blob)

    def get(self, service):
        return self.load_all().get(service)

Fernet is symmetric authenticated encryption from Python’s cryptography library, the vault on disk is unreadable bytes, and tampering fails decryption loudly. The master password itself is never stored anywhere, it exists only in the user’s head and, briefly, in memory to derive the key, which is the entire point, the file alone is worthless to whoever copies it.

Filling happens where the login forms live, inside the pages, and that is why the views were subclassed from the start. When a CustomWebView finishes loading a known service’s login page, it injects the credentials into the form fields through the page’s own scripting:

def autofill(self):
    creds = self.password_manager.get(self.service_name)
    if not creds:
        return
    js = (
        "var u=document.querySelector('input[name=log], input[type=email]');"
        "var p=document.querySelector('input[type=password]');"
        "if(u){u.value='%s';} if(p){p.value='%s';}"
    ) % (creds['u'], creds['p'])
    self.page().runJavaScript(js)

The service_name tag from the services map picks the right credentials per tab, the selectors cover the login forms of the actual four services, and the user clicks sign in themselves, the app fills, the human confirms. The honest boundaries, stated plainly. This design’s security equals the master password’s strength plus the machine’s security, it defends the file at rest, not a compromised computer. A production-grade vault would add salted key stretching for the master password, and the master gate doubles as the app’s own login, opening the vault and the cockpit in one step. Within those boundaries, the feature that began as remember my passwords became the app’s best daily magic, four services, one master password, every login one click.

A few things people ask me about this

Why not store passwords in the app’s settings or source? Because settings files and source travel, backups, copies, repositories, and plain text credentials travel with them. Encryption at rest makes every copy worthless without the master password.

How does the app type into a web page’s login form? Through the page itself, QWebEnginePage.runJavaScript sets the form fields’ values with selectors matching the service’s login form, the same page scripting any browser extension uses.

Next

That completes the cockpit, embedded, unified, packaged, and secured. The finale weighs what building a desktop shell for a real business taught me.

Leave a Reply

Your email address will not be published. Required fields are marked *