├── .gitignore
├── passlane.sh
├── src
├── ui
│ ├── mod.rs
│ ├── output.rs
│ └── input.rs
├── vault
│ ├── mod.rs
│ ├── vault_trait.rs
│ ├── entities.rs
│ └── keepass_vault.rs
├── actions
│ ├── generate.rs
│ ├── help.rs
│ ├── lock.rs
│ ├── unlock.rs
│ ├── import.rs
│ ├── export.rs
│ ├── add.rs
│ ├── mod.rs
│ ├── init.rs
│ ├── edit.rs
│ ├── delete.rs
│ └── show.rs
├── keychain.rs
├── crypto.rs
├── store.rs
└── main.rs
├── copy_schema.sh
├── test.csv
├── .idea
├── codeStyles
│ └── codeStyleConfig.xml
├── misc.xml
├── vcs.xml
├── .gitignore
├── modules.xml
└── passlane.iml
├── shell.nix
├── default.nix
├── flake.nix
├── flake.lock
├── SECURITY.md
├── Cargo.toml
├── .github
└── workflows
│ └── release.yml
├── CHANGELOG.md
├── TODO.md
├── README.md
└── LICENSE
/.gitignore:
--------------------------------------------------------------------------------
1 | .cargo/config.toml
2 | /target
3 |
--------------------------------------------------------------------------------
/passlane.sh:
--------------------------------------------------------------------------------
1 | #! /bin/sh
2 | /usr/local/bin/passlane
--------------------------------------------------------------------------------
/src/ui/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod input;
2 | pub mod output;
3 |
--------------------------------------------------------------------------------
/copy_schema.sh:
--------------------------------------------------------------------------------
1 | #! /bin/sh
2 | cp ../../passlanevault.com/graphql/schema.graphql ./src
--------------------------------------------------------------------------------
/src/vault/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod entities;
2 | pub mod vault_trait;
3 | pub mod keepass_vault;
4 |
--------------------------------------------------------------------------------
/test.csv:
--------------------------------------------------------------------------------
1 | username,password,service
2 | apina,123412345a,nakkila.net
3 | apina,123412345a,nakkinack.net
--------------------------------------------------------------------------------
/.idea/codeStyles/codeStyleConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 | # Editor-based HTTP Client requests
5 | /httpRequests/
6 | # Datasource local storage ignored files
7 | /dataSources/
8 | /dataSources.local.xml
9 | # GitHub Copilot persisted chat sessions
10 | /copilot/chatSessions
11 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/shell.nix:
--------------------------------------------------------------------------------
1 | { pkgs ? import { } }:
2 | pkgs.mkShell {
3 | # Get dependencies from the main package
4 | inputsFrom = [ (pkgs.callPackage ./default.nix { }) ];
5 | # Additional tooling
6 | buildInputs = with pkgs; [
7 | rust-analyzer # LSP Server
8 | rustfmt # Formatter
9 | clippy # Linter
10 | ];
11 | }
--------------------------------------------------------------------------------
/src/actions/generate.rs:
--------------------------------------------------------------------------------
1 | use crate::actions::{copy_to_clipboard, Action};
2 | use crate::crypto;
3 | use crate::vault::entities::Error;
4 |
5 | pub struct GeneratePasswordAction;
6 |
7 | impl Action for GeneratePasswordAction {
8 | fn run(&self) -> Result {
9 | let password = crypto::generate();
10 | copy_to_clipboard(&password);
11 | Ok("Password - also copied to clipboard".to_string())
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/default.nix:
--------------------------------------------------------------------------------
1 | { pkgs ? import { } }:
2 | let manifest = (pkgs.lib.importTOML ./Cargo.toml).package;
3 | in
4 | pkgs.rustPlatform.buildRustPackage rec {
5 | pname = manifest.name;
6 | version = manifest.version;
7 | cargoLock = {
8 | lockFile = ./Cargo.lock;
9 | };
10 | src = pkgs.lib.cleanSource ./.;
11 |
12 | buildInputs = [
13 | pkgs.darwin.apple_sdk.frameworks.CoreServices
14 | pkgs.darwin.apple_sdk.frameworks.AppKit
15 | ];
16 | }
--------------------------------------------------------------------------------
/.idea/passlane.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/flake.nix:
--------------------------------------------------------------------------------
1 | {
2 | description = "A password manager for the command line";
3 | inputs = {
4 | nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
5 | };
6 | outputs = { self, nixpkgs }:
7 | let
8 | supportedSystems = [ "aarch64-darwin" ];
9 | forAllSystems = nixpkgs.lib.genAttrs supportedSystems;
10 | pkgsFor = nixpkgs.legacyPackages;
11 | in {
12 | packages = forAllSystems (system: {
13 | default = pkgsFor.${system}.callPackage ./default.nix { };
14 | });
15 | devShells = forAllSystems (system: {
16 | default = pkgsFor.${system}.callPackage ./shell.nix { };
17 | });
18 | };
19 | }
--------------------------------------------------------------------------------
/flake.lock:
--------------------------------------------------------------------------------
1 | {
2 | "nodes": {
3 | "nixpkgs": {
4 | "locked": {
5 | "lastModified": 1711163522,
6 | "narHash": "sha256-YN/Ciidm+A0fmJPWlHBGvVkcarYWSC+s3NTPk/P+q3c=",
7 | "owner": "nixos",
8 | "repo": "nixpkgs",
9 | "rev": "44d0940ea560dee511026a53f0e2e2cde489b4d4",
10 | "type": "github"
11 | },
12 | "original": {
13 | "owner": "nixos",
14 | "ref": "nixos-unstable",
15 | "repo": "nixpkgs",
16 | "type": "github"
17 | }
18 | },
19 | "root": {
20 | "inputs": {
21 | "nixpkgs": "nixpkgs"
22 | }
23 | }
24 | },
25 | "root": "root",
26 | "version": 7
27 | }
28 |
--------------------------------------------------------------------------------
/src/actions/help.rs:
--------------------------------------------------------------------------------
1 | use clap::Command;
2 | use crate::actions::Action;
3 | use crate::vault::entities::Error;
4 |
5 | pub struct PrintHelpAction {
6 | cli: Command,
7 | }
8 |
9 | impl PrintHelpAction {
10 | pub fn new(cli: Command) -> PrintHelpAction {
11 | PrintHelpAction {
12 | cli
13 | }
14 | }
15 | }
16 |
17 | impl Action for PrintHelpAction {
18 | fn run(&self) -> Result {
19 | // write the help to a string
20 | let mut help_text = Vec::new();
21 | self.cli.clone().write_help(&mut help_text)?;
22 |
23 | String::from_utf8(help_text).map(|s| s.to_string()).map_err(|_| Error::new("Failed to convert help text to string"))
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/actions/lock.rs:
--------------------------------------------------------------------------------
1 | use crate::actions::Action;
2 | use crate::keychain;
3 | use crate::vault::entities::Error;
4 |
5 | pub struct LockAction {}
6 |
7 | impl Action for LockAction {
8 | fn run(&self) -> Result {
9 | let credential_vault_response = match keychain::delete_master_password() {
10 | Ok(_) => {
11 | "Vault locked"
12 | }
13 | Err(_) => {
14 | "Vault was already locked"
15 | }
16 | };
17 | let totp_vault_response = match keychain::delete_totp_master_password() {
18 | Ok(_) => {
19 | "TOTP vault locked"
20 | }
21 | Err(_) => {
22 | "TOTP vault was already locked"
23 | }
24 | };
25 | Ok(format!("{}\n{}", credential_vault_response, totp_vault_response))
26 | }
27 | }
--------------------------------------------------------------------------------
/src/actions/unlock.rs:
--------------------------------------------------------------------------------
1 | use clap::ArgMatches;
2 | use crate::actions::{Action, unlock, unlock_totp_vault};
3 | use crate::keychain;
4 | use crate::vault::entities::Error;
5 |
6 | pub struct UnlockAction {
7 | pub totp: bool,
8 | }
9 |
10 | impl UnlockAction {
11 | pub fn new(matches: &ArgMatches) -> UnlockAction {
12 | UnlockAction {
13 | totp: matches.get_one::("otp").map_or(false, |v| *v),
14 | }
15 | }
16 | }
17 |
18 | impl Action for UnlockAction {
19 | fn run(&self) -> Result {
20 | if self.totp {
21 | let vault = unlock_totp_vault()?;
22 | keychain::save_totp_master_password(&vault.get_master_password())?;
23 | } else {
24 | let vault = unlock()?;
25 | keychain::save_master_password(&vault.get_master_password())?;
26 | }
27 | Ok("Vault unlocked".to_string())
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 |
3 | Passlane is free software and is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose and noninfringement. In no event shall the authors or copyright holders be liable for any claim, damages or other liability, whether in an action of contract, tort or otherwise, arising from, out of or in connection with the software or the use or other dealings in the software.
4 |
5 | ## Supported Versions
6 |
7 | Only the current major version will receive security updates.
8 |
9 | ## Reporting a Vulnerability
10 |
11 | Please use the GitHub private vulnerability reporting features to report vulnerability. See the [GitHub docs](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing/privately-reporting-a-security-vulnerability) for more details.
12 |
13 | Since keepass-rs is a volunteer project, vulnerabilities will be addressed on a best effort basis, with no guarantees made on timelines to resolution.
14 |
--------------------------------------------------------------------------------
/src/actions/import.rs:
--------------------------------------------------------------------------------
1 | use crate::actions::UnlockingAction;
2 | use crate::store;
3 | use crate::vault::entities::Error;
4 | use crate::vault::vault_trait::Vault;
5 | use clap::ArgMatches;
6 |
7 | pub struct ImportCsvAction {
8 | pub file_path: String,
9 | }
10 |
11 | impl ImportCsvAction {
12 | pub fn new(matches: &ArgMatches) -> ImportCsvAction {
13 | ImportCsvAction {
14 | file_path: matches
15 | .get_one::("FILE_PATH")
16 | .expect("required")
17 | .to_string(),
18 | }
19 | }
20 | }
21 |
22 | fn push_from_csv(vault: &mut Box, file_path: &str) -> Result {
23 | let creds = store::read_from_csv(file_path)?;
24 | vault.save_credentials(&creds)?;
25 | let num_imported = creds.len();
26 | Ok(num_imported.try_into().unwrap())
27 | }
28 |
29 | impl UnlockingAction for ImportCsvAction {
30 | fn run_with_vault(&self, vault: &mut Box) -> Result