├── .github └── workflows │ └── release.yml ├── .gitignore ├── .goreleaser.yml ├── README.md ├── autocomplete ├── bash_autocomplete ├── powershell_autocomplete.ps1 └── zsh_autocomplete ├── cmd └── passwork │ └── passwork.go ├── go.mod ├── go.sum └── pkg ├── action ├── auth.go ├── folders.go ├── helpers.go ├── passwords.go ├── user.go └── vaults.go └── command ├── auth.go ├── folders.go ├── helpers.go ├── passwords.go ├── user.go └── vaults.go /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | on: 4 | push: 5 | tags: 6 | - v*.*.* 7 | 8 | jobs: 9 | goreleaser: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v2 14 | with: 15 | fetch-depth: 0 16 | - name: Set up Go 17 | uses: actions/setup-go@v2 18 | with: 19 | go-version: "1.20" 20 | - name: Run GoReleaser 21 | uses: goreleaser/goreleaser-action@v2 22 | with: 23 | # either 'goreleaser' (default) or 'goreleaser-pro' 24 | distribution: goreleaser 25 | version: latest 26 | args: release --clean 27 | env: 28 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 29 | HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }} 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .passwork* 2 | dist/ 3 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | before: 2 | hooks: 3 | - go mod tidy 4 | builds: 5 | - main: ./cmd/passwork 6 | #id: passwork 7 | binary: passwork 8 | env: 9 | - CGO_ENABLED=0 10 | goos: 11 | - linux 12 | - windows 13 | - darwin 14 | goarch: 15 | - amd64 16 | - arm64 17 | ldflags: 18 | - -s -w -X main.AppVersion={{.Version}} 19 | archives: 20 | - files: 21 | - "autocomplete/*" 22 | rlcp: true 23 | # format: binary 24 | checksum: 25 | name_template: "checksums.txt" 26 | snapshot: 27 | name_template: "{{ incpatch .Version }}-next" 28 | changelog: 29 | sort: asc 30 | filters: 31 | exclude: 32 | - "^docs:" 33 | - "^test:" 34 | brews: 35 | - tap: 36 | owner: Sebor 37 | name: homebrew-tap 38 | token: "{{ .Env.HOMEBREW_TAP_GITHUB_TOKEN }}" 39 | 40 | commit_msg_template: "Brew formula update for {{ .ProjectName }} version {{ .Tag }}" 41 | 42 | folder: Formula 43 | 44 | homepage: "https://github.com/Sebor/passwork-cli" 45 | description: "Command Line Interface tool for https://passwork.me/" 46 | license: "MIT" 47 | 48 | install: | 49 | bin.install "passwork" 50 | bash_completion.install "autocomplete/bash_autocomplete" => "passwork" 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Passwork CLI 2 | 3 | **passwork-cli** - is a CLI tool to interact with [passwork]() API. 4 | 5 | There is no API public documentation but official js [client]() is presented. 6 | 7 | ## Currently supported methods 8 | 9 | * Auth 10 | * login 11 | * logout 12 | * Folders 13 | * add 14 | * get 15 | * edit 16 | * delete 17 | * passwords 18 | * search 19 | * children 20 | * Passwords 21 | * add 22 | * get 23 | * search 24 | * delete 25 | * edit 26 | * copy 27 | * move 28 | * get recent 29 | * get favorite 30 | * make favorite 31 | * make unfavorite 32 | * search by url 33 | * Users 34 | * get 35 | * Vaults 36 | * list 37 | * tags 38 | * domain 39 | * folders 40 | * passwords 41 | 42 | ## Homebrew Installation 43 | 44 | ```bash 45 | brew tap Sebor/tap 46 | brew install passwork-cli 47 | ``` 48 | 49 | ## Build 50 | 51 | ```bash 52 | go build cmd/passwork/passwork.go 53 | ``` 54 | 55 | ## Help 56 | 57 | ```bash 58 | ./passwork -h 59 | ``` 60 | 61 | ## Usage 62 | 63 | You can set Passwork API URL / API Key by multiple ways: 64 | 65 | * Using options `--api-url` and `--api-key` 66 | * Setting env variables `$PASSWORK_API_URL` and `$PASSWORK_API_KEY` 67 | * Creating files `.passwork-api-url` and `.passwork-api-key` within executed(current) directory 68 | 69 | ## Auto-completion 70 | 71 | ### Bash 72 | 73 | #### Enabling 74 | 75 | To enable auto-completion for the current shell session, a bash script, 76 | `autocomplete/bash_autocomplete` is included in this repo. 77 | 78 | To use `autocomplete/bash_autocomplete` set an environment variable named `PROG` to 79 | the name of your program and then `source` the `autocomplete/bash_autocomplete` file. 80 | 81 | For example, if your cli program is called `passwork`: 82 | 83 | `PROG=passwork source path/to/cli/autocomplete/bash_autocomplete` 84 | 85 | Auto-completion is now enabled for the current shell, but will not persist into a new shell. 86 | 87 | #### Distribution and Persistent Autocompletion 88 | 89 | Copy `autocomplete/bash_autocomplete` into `/etc/bash_completion.d/` and rename 90 | it to the name of the program you wish to add autocomplete support for (or 91 | automatically install it there if you are distributing a package). Don't forget 92 | to source the file or restart your shell to activate the auto-completion. 93 | 94 | ```bash 95 | sudo cp path/to/autocomplete/bash_autocomplete /etc/bash_completion.d/passwork 96 | source /etc/bash_completion.d/passwork 97 | ``` 98 | 99 | Alternatively, you can just document that users should `source` the generic 100 | `autocomplete/bash_autocomplete` and set `$PROG` within their bash configuration 101 | file, adding these lines: 102 | 103 | ```bash 104 | PROG=passwork 105 | source path/to/cli/autocomplete/bash_autocomplete 106 | ``` 107 | 108 | Keep in mind that if they are enabling auto-completion for more than one program, 109 | they will need to set `PROG` and source `autocomplete/bash_autocomplete` for each 110 | program, like so: 111 | 112 | ```bash 113 | PROG= 114 | source path/to/cli/autocomplete/bash_autocomplete 115 | PROG= 116 | source path/to/cli/autocomplete/bash_autocomplete 117 | ``` 118 | 119 | ### ZSH 120 | 121 | Auto-completion for ZSH is also supported using the `autocomplete/zsh_autocomplete` 122 | file included in this repo. Two environment variables are used, `PROG` and `_CLI_ZSH_AUTOCOMPLETE_HACK`. 123 | Set `PROG` to the program name as before, set `_CLI_ZSH_AUTOCOMPLETE_HACK` to `1`, and 124 | then `source path/to/autocomplete/zsh_autocomplete`. Adding the following lines to your ZSH 125 | configuration file (usually `.zshrc`) will allow the auto-completion to persist across new shells: 126 | 127 | ```zsh 128 | PROG=passwork 129 | _CLI_ZSH_AUTOCOMPLETE_HACK=1 130 | source path/to/autocomplete/zsh_autocomplete 131 | ``` 132 | 133 | ### PowerShell 134 | 135 | Auto-completion for PowerShell is also supported using the `autocomplete/powershell_autocomplete.ps1` 136 | file included in this repo. 137 | 138 | Rename the script to `.ps1` and move it anywhere in your file system. 139 | The location of script does not matter, only the file name of the script has to match 140 | the your program's binary name. 141 | 142 | To activate it, enter `& path/to/autocomplete/.ps1` 143 | 144 | To persist across new shells, open the PowerShell profile (with `code $profile` or `notepad $profile`) 145 | and add the line: 146 | 147 | ```ps 148 | & path/to/autocomplete/.ps1 149 | ``` 150 | 151 | ## Roadmap 152 | 153 | * Add remaining methods 154 | * Add auto login feature when access token is expired 155 | * Add github actions 156 | * Add handling of http response code 157 | -------------------------------------------------------------------------------- /autocomplete/bash_autocomplete: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | : ${PROG:=$(basename ${BASH_SOURCE})} 4 | 5 | # Macs have bash3 for which the bash-completion package doesn't include 6 | # _init_completion. This is a minimal version of that function. 7 | _cli_init_completion() { 8 | COMPREPLY=() 9 | _get_comp_words_by_ref "$@" cur prev words cword 10 | } 11 | 12 | _cli_bash_autocomplete() { 13 | if [[ "${COMP_WORDS[0]}" != "source" ]]; then 14 | local cur opts base words 15 | COMPREPLY=() 16 | cur="${COMP_WORDS[COMP_CWORD]}" 17 | if declare -F _init_completion >/dev/null 2>&1; then 18 | _init_completion -n "=:" || return 19 | else 20 | _cli_init_completion -n "=:" || return 21 | fi 22 | words=("${words[@]:0:$cword}") 23 | if [[ "$cur" == "-"* ]]; then 24 | requestComp="${words[*]} ${cur} --generate-shell-completion" 25 | else 26 | requestComp="${words[*]} --generate-shell-completion" 27 | fi 28 | opts=$(eval "${requestComp}" 2>/dev/null) 29 | COMPREPLY=($(compgen -W "${opts}" -- ${cur})) 30 | return 0 31 | fi 32 | } 33 | 34 | complete -o bashdefault -o default -o nospace -F _cli_bash_autocomplete $PROG 35 | unset PROG 36 | -------------------------------------------------------------------------------- /autocomplete/powershell_autocomplete.ps1: -------------------------------------------------------------------------------- 1 | $fn = $($MyInvocation.MyCommand.Name) 2 | $name = $fn -replace "(.*)\.ps1$", '$1' 3 | Register-ArgumentCompleter -Native -CommandName $name -ScriptBlock { 4 | param($commandName, $wordToComplete, $cursorPosition) 5 | $other = "$wordToComplete --generate-shell-completion" 6 | Invoke-Expression $other | ForEach-Object { 7 | [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /autocomplete/zsh_autocomplete: -------------------------------------------------------------------------------- 1 | #compdef $PROG 2 | 3 | _cli_zsh_autocomplete() { 4 | local -a opts 5 | local cur 6 | cur=${words[-1]} 7 | if [[ "$cur" == "-"* ]]; then 8 | opts=("${(@f)$(${words[@]:0:#words[@]-1} ${cur} --generate-shell-completion)}") 9 | else 10 | opts=("${(@f)$(${words[@]:0:#words[@]-1} --generate-shell-completion)}") 11 | fi 12 | 13 | if [[ "${opts[1]}" != "" ]]; then 14 | _describe 'values' opts 15 | else 16 | _files 17 | fi 18 | } 19 | 20 | compdef _cli_zsh_autocomplete $PROG 21 | -------------------------------------------------------------------------------- /cmd/passwork/passwork.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "password-cli/pkg/command" 8 | 9 | "github.com/urfave/cli/v2" 10 | ) 11 | 12 | var ( 13 | APIURLFilename = ".passwork-api-url" 14 | 15 | AppUsage = "Passwork Command Line Interface tool (https://passwork.me)" 16 | AppAuthors = []*cli.Author{ 17 | { 18 | Name: "Sebor", 19 | Email: "sebor@sebor.pro", 20 | }, 21 | } 22 | AppVersion = "v1.0.0" 23 | AppHelpTemplate = `NAME: 24 | {{.Name}} - {{.Usage}} 25 | 26 | USAGE: 27 | {{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}} 28 | {{if len .Authors}} 29 | AUTHOR: 30 | {{range .Authors}}{{ . }}{{end}} 31 | {{end}} 32 | {{if .Commands}} 33 | COMMANDS: 34 | {{range .Commands}}{{if not .HideHelp}} {{join .Names ", "}}{{ "\t"}}{{.Usage}}{{ "\n" }}{{end}}{{end}}{{end}}{{if .VisibleFlags}} 35 | GLOBAL OPTIONS: 36 | {{range .VisibleFlags}}{{.}} 37 | {{end}}{{end}} 38 | {{if .Copyright }} 39 | COPYRIGHT: 40 | {{.Copyright}} 41 | {{end}} 42 | {{if .Version}} 43 | VERSION: 44 | {{.Version}} 45 | {{end}} 46 | ` 47 | ) 48 | 49 | func main() { 50 | cli.AppHelpTemplate = AppHelpTemplate 51 | 52 | // Global Flags 53 | flags := []cli.Flag{ 54 | &cli.StringFlag{ 55 | Name: "api-url", 56 | Aliases: []string{"au"}, 57 | Usage: "Passwork API URL", 58 | EnvVars: []string{"PASSWORK_API_URL"}, 59 | FilePath: ".passwork-api-url", 60 | Required: true, 61 | }, 62 | &cli.StringFlag{ 63 | Name: "tokenfile", 64 | Aliases: []string{"tf"}, 65 | Value: ".passwork-token", 66 | Usage: "Filename inside $HOME/ dir to store API token", 67 | EnvVars: []string{"PASSWORK_TOKEN_FILENAME"}, 68 | }, 69 | } 70 | 71 | app := &cli.App{ 72 | Usage: AppUsage, 73 | Version: AppVersion, 74 | Authors: AppAuthors, 75 | Flags: flags, 76 | Commands: command.Commands, 77 | EnableBashCompletion: true, 78 | } 79 | 80 | err := app.Run(os.Args) 81 | if err != nil { 82 | fmt.Fprintf(os.Stderr, "%v\n", err) 83 | os.Exit(1) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module password-cli 2 | 3 | go 1.20 4 | 5 | require github.com/urfave/cli/v2 v2.25.0 6 | 7 | require ( 8 | github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect 9 | github.com/russross/blackfriday/v2 v2.1.0 // indirect 10 | github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect 11 | ) 12 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= 2 | github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 3 | github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= 4 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 5 | github.com/urfave/cli/v2 v2.25.0 h1:ykdZKuQey2zq0yin/l7JOm9Mh+pg72ngYMeB0ABn6q8= 6 | github.com/urfave/cli/v2 v2.25.0/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc= 7 | github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= 8 | github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= 9 | -------------------------------------------------------------------------------- /pkg/action/auth.go: -------------------------------------------------------------------------------- 1 | package action 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | var ( 8 | AUTH_URI_PREFIX = "/auth" 9 | ) 10 | 11 | type Auth struct { 12 | APIkey string 13 | Config GlobalConfig 14 | } 15 | 16 | func (a *Auth) AuthLogin() error { 17 | url := a.Config.APIUrl + AUTH_URI_PREFIX + "/login/" + a.APIkey 18 | body, err := callAPI(url, "POST", "", nil) 19 | if err != nil { 20 | return fmt.Errorf("cannot call API: %s", err.Error()) 21 | } 22 | 23 | a.Config.WriteToken(body) 24 | 25 | return nil 26 | } 27 | 28 | func (a *Auth) AuthLogout() error { 29 | url := a.Config.APIUrl + AUTH_URI_PREFIX + "/logout" 30 | _, err := callAPI(url, "POST", a.Config.ReadToken(), nil) 31 | if err != nil { 32 | return fmt.Errorf("cannot call API: %s", err.Error()) 33 | } 34 | 35 | return nil 36 | } 37 | -------------------------------------------------------------------------------- /pkg/action/folders.go: -------------------------------------------------------------------------------- 1 | package action 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | ) 8 | 9 | var ( 10 | FOLDERS_URI_PREFIX = "/folders" 11 | ) 12 | 13 | type Folder struct { 14 | Name string `json:"name"` 15 | VaultId string `json:"vaultId,omitempty"` 16 | Config GlobalConfig `json:"-"` 17 | } 18 | 19 | type FolderSearchQuery struct { 20 | Query string `json:"query"` 21 | VaultId string `json:"vaultId,omitempty"` 22 | Config GlobalConfig `json:"-"` 23 | } 24 | 25 | func (f *Folder) FolderAdd() error { 26 | data, err := json.Marshal(f) 27 | if err != nil { 28 | return fmt.Errorf("cannot dump data: %s", err.Error()) 29 | } 30 | 31 | url := f.Config.APIUrl + FOLDERS_URI_PREFIX 32 | _, err = callAPI(url, "POST", f.Config.ReadToken(), bytes.NewReader(data)) 33 | if err != nil { 34 | return fmt.Errorf("cannot call API: %s", err.Error()) 35 | } 36 | 37 | return nil 38 | } 39 | 40 | func (f *Folder) FolderGet(id string) error { 41 | url := f.Config.APIUrl + FOLDERS_URI_PREFIX + "/" + id 42 | _, err := callAPI(url, "GET", f.Config.ReadToken(), nil) 43 | if err != nil { 44 | return fmt.Errorf("cannot call API: %s", err.Error()) 45 | } 46 | 47 | return nil 48 | } 49 | 50 | func (f *Folder) FolderEdit(id string) error { 51 | data, err := json.Marshal(f) 52 | if err != nil { 53 | return fmt.Errorf("cannot dump data: %s", err.Error()) 54 | } 55 | 56 | url := f.Config.APIUrl + FOLDERS_URI_PREFIX + "/" + id 57 | _, err = callAPI(url, "PUT", f.Config.ReadToken(), bytes.NewReader(data)) 58 | if err != nil { 59 | return fmt.Errorf("cannot call API: %s", err.Error()) 60 | } 61 | 62 | return nil 63 | } 64 | 65 | func (f *Folder) FolderDelete(id string) error { 66 | url := f.Config.APIUrl + FOLDERS_URI_PREFIX + "/" + id 67 | _, err := callAPI(url, "DELETE", f.Config.ReadToken(), nil) 68 | if err != nil { 69 | return fmt.Errorf("cannot call API: %s", err.Error()) 70 | } 71 | 72 | return nil 73 | } 74 | 75 | func (f *Folder) FolderGetPasswords(id string) error { 76 | url := f.Config.APIUrl + FOLDERS_URI_PREFIX + "/" + id + "/passwords" 77 | _, err := callAPI(url, "GET", f.Config.ReadToken(), nil) 78 | if err != nil { 79 | return fmt.Errorf("cannot call API: %s", err.Error()) 80 | } 81 | 82 | return nil 83 | } 84 | 85 | func (f *FolderSearchQuery) FolderSearch() error { 86 | data, err := json.Marshal(f) 87 | if err != nil { 88 | return fmt.Errorf("cannot dump data: %s", err.Error()) 89 | } 90 | 91 | url := f.Config.APIUrl + FOLDERS_URI_PREFIX + "/search" 92 | _, err = callAPI(url, "POST", f.Config.ReadToken(), bytes.NewReader(data)) 93 | if err != nil { 94 | return fmt.Errorf("cannot call API: %s", err.Error()) 95 | } 96 | 97 | return nil 98 | } 99 | 100 | func (f *Folder) FolderGetChildren(id string) error { 101 | url := f.Config.APIUrl + FOLDERS_URI_PREFIX + "/" + id + "/children" 102 | _, err := callAPI(url, "GET", f.Config.ReadToken(), nil) 103 | if err != nil { 104 | return fmt.Errorf("cannot call API: %s", err.Error()) 105 | } 106 | 107 | return nil 108 | } 109 | -------------------------------------------------------------------------------- /pkg/action/helpers.go: -------------------------------------------------------------------------------- 1 | package action 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | "os" 9 | "path/filepath" 10 | "strings" 11 | "time" 12 | ) 13 | 14 | type APIToken struct { 15 | Status string `json:"status"` 16 | Data TokenData `json:"data"` 17 | } 18 | 19 | type TokenData struct { 20 | Token string `json:"token"` 21 | TokenExpiredAt int64 `json:"tokenExpiredAt"` 22 | } 23 | 24 | type GlobalConfig struct { 25 | APIUrl string 26 | TokenFile string 27 | } 28 | 29 | func NewGlobalConfig(apiURL, tokenFile string) *GlobalConfig { 30 | return &GlobalConfig{ 31 | APIUrl: strings.TrimSpace(apiURL), 32 | TokenFile: strings.TrimSpace(tokenFile), 33 | } 34 | } 35 | 36 | func callAPI(url, method, token string, data io.Reader) ([]byte, error) { 37 | client := &http.Client{ 38 | Timeout: time.Second * 5, 39 | } 40 | 41 | req, err := http.NewRequest(method, strings.TrimSpace(url), data) 42 | if err != nil { 43 | return nil, fmt.Errorf("cannot create request: %s", err.Error()) 44 | } 45 | 46 | req.Header.Add("Passwork-Auth", token) 47 | req.Header.Add("Accept", "application/json") 48 | req.Header.Add("Content-Type", "application/json") 49 | resp, err := client.Do(req) 50 | if err != nil { 51 | return nil, fmt.Errorf("cannot do request: %s", err.Error()) 52 | } 53 | 54 | defer resp.Body.Close() 55 | 56 | body, err := io.ReadAll(resp.Body) 57 | if err != nil { 58 | return nil, fmt.Errorf("cannot read response body: %s", err.Error()) 59 | } 60 | 61 | fmt.Println(string(body)) 62 | 63 | return body, nil 64 | } 65 | 66 | func (g *GlobalConfig) ReadToken() string { 67 | homeDir, err := os.UserHomeDir() 68 | if err != nil { 69 | fmt.Println(err.Error()) 70 | os.Exit(1) 71 | } 72 | 73 | tokenFilepath := filepath.Join(homeDir, g.TokenFile) 74 | data, err := os.ReadFile(tokenFilepath) 75 | if err != nil { 76 | fmt.Println(err.Error()) 77 | os.Exit(1) 78 | } 79 | 80 | token := APIToken{} 81 | err = json.Unmarshal(data, &token) 82 | if err != nil { 83 | fmt.Println(err.Error()) 84 | os.Exit(1) 85 | } 86 | 87 | return token.Data.Token 88 | } 89 | 90 | func (g *GlobalConfig) WriteToken(body []byte) { 91 | homeDir, err := os.UserHomeDir() 92 | if err != nil { 93 | fmt.Println(err.Error()) 94 | os.Exit(1) 95 | } 96 | 97 | tokenFilepath := filepath.Join(homeDir, g.TokenFile) 98 | err = os.WriteFile(tokenFilepath, body, 0600) 99 | if err != nil { 100 | fmt.Println(err.Error()) 101 | os.Exit(1) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /pkg/action/passwords.go: -------------------------------------------------------------------------------- 1 | package action 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | ) 8 | 9 | var ( 10 | PASSWORDS_URI_PREFIX = "/passwords" 11 | ) 12 | 13 | type Password struct { 14 | Name string `json:"name,omitempty"` 15 | Login string `json:"login,omitempty"` 16 | CryptedPassword string `json:"cryptedPassword,omitempty"` 17 | Description string `json:"description,omitempty"` 18 | Url string `json:"url,omitempty"` 19 | MasterHash string `json:"masterHash,omitempty"` 20 | FolderId string `json:"folderId,omitempty"` 21 | VaultId string `json:"vaultId,omitempty"` 22 | Color int `json:"color,omitempty"` 23 | Tags []string `json:"tags,omitempty"` 24 | VaultTo string `json:"vaultTo,omitempty"` 25 | FolderTo string `json:"folderTo,omitempty"` 26 | Config GlobalConfig `json:"-"` 27 | } 28 | 29 | type PasswordSearchQuery struct { 30 | Query string `json:"query"` 31 | VaultId string `json:"vaultId,omitempty"` 32 | Colors []int `json:"colors,omitempty"` 33 | Tags []string `json:"tags,omitempty"` 34 | Config GlobalConfig `json:"-"` 35 | } 36 | 37 | func (p *Password) PasswordAdd() error { 38 | data, err := json.Marshal(p) 39 | if err != nil { 40 | return fmt.Errorf("cannot dump data: %s", err.Error()) 41 | } 42 | 43 | url := p.Config.APIUrl + PASSWORDS_URI_PREFIX 44 | _, err = callAPI(url, "POST", p.Config.ReadToken(), bytes.NewReader(data)) 45 | if err != nil { 46 | return fmt.Errorf("cannot call API: %s", err.Error()) 47 | } 48 | 49 | return nil 50 | } 51 | 52 | func (p *Password) PasswordGet(id string) error { 53 | url := p.Config.APIUrl + PASSWORDS_URI_PREFIX + "/" + id 54 | _, err := callAPI(url, "GET", p.Config.ReadToken(), nil) 55 | if err != nil { 56 | return fmt.Errorf("cannot call API: %s", err.Error()) 57 | } 58 | 59 | return nil 60 | } 61 | 62 | func (p *Password) PasswordDelete(id string) error { 63 | url := p.Config.APIUrl + PASSWORDS_URI_PREFIX + "/" + id 64 | _, err := callAPI(url, "DELETE", p.Config.ReadToken(), nil) 65 | if err != nil { 66 | return fmt.Errorf("cannot call API: %s", err.Error()) 67 | } 68 | 69 | return nil 70 | } 71 | 72 | func (p *PasswordSearchQuery) PasswordSearch() error { 73 | data, err := json.Marshal(p) 74 | if err != nil { 75 | return fmt.Errorf("cannot dump data: %s", err.Error()) 76 | } 77 | 78 | url := p.Config.APIUrl + PASSWORDS_URI_PREFIX + "/search" 79 | _, err = callAPI(url, "POST", p.Config.ReadToken(), bytes.NewReader(data)) 80 | if err != nil { 81 | return fmt.Errorf("cannot call API: %s", err.Error()) 82 | } 83 | 84 | return nil 85 | } 86 | 87 | func (p *Password) PasswordEdit(id string) error { 88 | data, err := json.Marshal(p) 89 | if err != nil { 90 | return fmt.Errorf("cannot dump data: %s", err.Error()) 91 | } 92 | 93 | url := p.Config.APIUrl + PASSWORDS_URI_PREFIX + "/" + id 94 | _, err = callAPI(url, "PUT", p.Config.ReadToken(), bytes.NewReader(data)) 95 | if err != nil { 96 | return fmt.Errorf("cannot call API: %s", err.Error()) 97 | } 98 | 99 | return nil 100 | } 101 | 102 | func (p *Password) PasswordCopy(id string) error { 103 | data, err := json.Marshal(p) 104 | if err != nil { 105 | return fmt.Errorf("cannot dump data: %s", err.Error()) 106 | } 107 | 108 | url := p.Config.APIUrl + PASSWORDS_URI_PREFIX + "/" + id + "/copy" 109 | _, err = callAPI(url, "POST", p.Config.ReadToken(), bytes.NewReader(data)) 110 | if err != nil { 111 | return fmt.Errorf("cannot call API: %s", err.Error()) 112 | } 113 | 114 | return nil 115 | } 116 | 117 | func (p *Password) PasswordMove(id string) error { 118 | data, err := json.Marshal(p) 119 | if err != nil { 120 | return fmt.Errorf("cannot dump data: %s", err.Error()) 121 | } 122 | 123 | url := p.Config.APIUrl + PASSWORDS_URI_PREFIX + "/" + id + "/move" 124 | _, err = callAPI(url, "POST", p.Config.ReadToken(), bytes.NewReader(data)) 125 | if err != nil { 126 | return fmt.Errorf("cannot call API: %s", err.Error()) 127 | } 128 | 129 | return nil 130 | } 131 | 132 | func (p *Password) PasswordGetRecent() error { 133 | url := p.Config.APIUrl + PASSWORDS_URI_PREFIX + "/recent" 134 | _, err := callAPI(url, "GET", p.Config.ReadToken(), nil) 135 | if err != nil { 136 | return fmt.Errorf("cannot call API: %s", err.Error()) 137 | } 138 | 139 | return nil 140 | } 141 | 142 | func (p *Password) PasswordGetFavorite() error { 143 | url := p.Config.APIUrl + PASSWORDS_URI_PREFIX + "/favorite" 144 | _, err := callAPI(url, "GET", p.Config.ReadToken(), nil) 145 | if err != nil { 146 | return fmt.Errorf("cannot call API: %s", err.Error()) 147 | } 148 | 149 | return nil 150 | } 151 | 152 | func (p *Password) PasswordMakeFavorite(id string) error { 153 | data, err := json.Marshal(p) 154 | if err != nil { 155 | return fmt.Errorf("cannot dump data: %s", err.Error()) 156 | } 157 | 158 | url := p.Config.APIUrl + PASSWORDS_URI_PREFIX + "/" + id + "/favorite" 159 | _, err = callAPI(url, "POST", p.Config.ReadToken(), bytes.NewReader(data)) 160 | if err != nil { 161 | return fmt.Errorf("cannot call API: %s", err.Error()) 162 | } 163 | 164 | return nil 165 | } 166 | 167 | func (p *Password) PasswordMakeUnFavorite(id string) error { 168 | data, err := json.Marshal(p) 169 | if err != nil { 170 | return fmt.Errorf("cannot dump data: %s", err.Error()) 171 | } 172 | 173 | url := p.Config.APIUrl + PASSWORDS_URI_PREFIX + "/" + id + "/unfavorite" 174 | _, err = callAPI(url, "POST", p.Config.ReadToken(), bytes.NewReader(data)) 175 | if err != nil { 176 | return fmt.Errorf("cannot call API: %s", err.Error()) 177 | } 178 | 179 | return nil 180 | } 181 | 182 | func (p *Password) PasswordSearchByURL() error { 183 | data, err := json.Marshal(p) 184 | if err != nil { 185 | return fmt.Errorf("cannot dump data: %s", err.Error()) 186 | } 187 | 188 | url := p.Config.APIUrl + PASSWORDS_URI_PREFIX + "/searchByUrl" 189 | _, err = callAPI(url, "POST", p.Config.ReadToken(), bytes.NewReader(data)) 190 | if err != nil { 191 | return fmt.Errorf("cannot call API: %s", err.Error()) 192 | } 193 | 194 | return nil 195 | } 196 | -------------------------------------------------------------------------------- /pkg/action/user.go: -------------------------------------------------------------------------------- 1 | package action 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | var ( 8 | USER_URI_PREFIX = "/user" 9 | ) 10 | 11 | type User struct { 12 | Config GlobalConfig 13 | } 14 | 15 | func (u *User) UserGet() error { 16 | url := u.Config.APIUrl + USER_URI_PREFIX + "/info" 17 | _, err := callAPI(url, "GET", u.Config.ReadToken(), nil) 18 | if err != nil { 19 | return fmt.Errorf("cannot call API: %s", err.Error()) 20 | } 21 | 22 | return nil 23 | } 24 | -------------------------------------------------------------------------------- /pkg/action/vaults.go: -------------------------------------------------------------------------------- 1 | package action 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | var ( 8 | VAULTS_URI_PREFIX = "/vaults" 9 | ) 10 | 11 | type Vault struct { 12 | Name string `json:"name"` 13 | DomainId string `json:"domainId"` 14 | MPCrypted string `json:"mpCrypted"` 15 | PasswordHash string `json:"passwordHash"` 16 | Salt string `json:"salt"` 17 | PasswordCrypted string `json:"passwordCrypted"` 18 | Config GlobalConfig `json:"-"` 19 | } 20 | 21 | func (v *Vault) VaultList() error { 22 | url := v.Config.APIUrl + VAULTS_URI_PREFIX + "/list" 23 | _, err := callAPI(url, "GET", v.Config.ReadToken(), nil) 24 | if err != nil { 25 | return fmt.Errorf("cannot call API: %s", err.Error()) 26 | } 27 | 28 | return nil 29 | } 30 | 31 | func (v *Vault) VaultAllTags() error { 32 | url := v.Config.APIUrl + VAULTS_URI_PREFIX + "/tags" 33 | _, err := callAPI(url, "GET", v.Config.ReadToken(), nil) 34 | if err != nil { 35 | return fmt.Errorf("cannot call API: %s", err.Error()) 36 | } 37 | 38 | return nil 39 | } 40 | 41 | func (v *Vault) VaultDomain() error { 42 | url := v.Config.APIUrl + VAULTS_URI_PREFIX + "/domain" 43 | _, err := callAPI(url, "GET", v.Config.ReadToken(), nil) 44 | if err != nil { 45 | return fmt.Errorf("cannot call API: %s", err.Error()) 46 | } 47 | 48 | return nil 49 | } 50 | 51 | func (v *Vault) VaultGetFolders(id string) error { 52 | url := v.Config.APIUrl + VAULTS_URI_PREFIX + "/" + id + "/folders" 53 | _, err := callAPI(url, "GET", v.Config.ReadToken(), nil) 54 | if err != nil { 55 | return fmt.Errorf("cannot call API: %s", err.Error()) 56 | } 57 | 58 | return nil 59 | } 60 | 61 | func (v *Vault) VaultGetPasswords(id string) error { 62 | url := v.Config.APIUrl + VAULTS_URI_PREFIX + "/" + id + "/passwords" 63 | _, err := callAPI(url, "GET", v.Config.ReadToken(), nil) 64 | if err != nil { 65 | return fmt.Errorf("cannot call API: %s", err.Error()) 66 | } 67 | 68 | return nil 69 | } 70 | 71 | func (v *Vault) VaultGetFullInfo(id string) error { 72 | url := v.Config.APIUrl + VAULTS_URI_PREFIX + "/" + id + "/fullInfo" 73 | _, err := callAPI(url, "GET", v.Config.ReadToken(), nil) 74 | if err != nil { 75 | return fmt.Errorf("cannot call API: %s", err.Error()) 76 | } 77 | 78 | return nil 79 | } 80 | 81 | func (v *Vault) VaultGetTags(id string) error { 82 | url := v.Config.APIUrl + VAULTS_URI_PREFIX + "/" + id + "/tags" 83 | _, err := callAPI(url, "GET", v.Config.ReadToken(), nil) 84 | if err != nil { 85 | return fmt.Errorf("cannot call API: %s", err.Error()) 86 | } 87 | 88 | return nil 89 | } 90 | -------------------------------------------------------------------------------- /pkg/command/auth.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "password-cli/pkg/action" 5 | 6 | "github.com/urfave/cli/v2" 7 | ) 8 | 9 | var AuthCommands = cli.Command{ 10 | Name: "auth", 11 | Aliases: []string{"a"}, 12 | Usage: "Passwork auth section", 13 | Subcommands: []*cli.Command{ 14 | { 15 | Name: "login", 16 | Aliases: []string{"li"}, 17 | Usage: "Fetch session token by API key", 18 | Flags: []cli.Flag{ 19 | &cli.StringFlag{ 20 | Name: "api-key", 21 | Aliases: []string{"ak"}, 22 | Usage: "Passwork API key", 23 | DefaultText: "$PASSWORK_API_KEY", 24 | EnvVars: []string{"PASSWORK_API_KEY"}, 25 | FilePath: ".passwork-api-key", 26 | Required: true, 27 | }, 28 | }, 29 | Action: func(c *cli.Context) error { 30 | a := action.Auth{ 31 | APIkey: c.String("api-key"), 32 | Config: *action.NewGlobalConfig( 33 | c.String("api-url"), 34 | c.String("tokenfile"), 35 | ), 36 | } 37 | err := a.AuthLogin() 38 | if err != nil { 39 | return err 40 | } 41 | return nil 42 | }, 43 | }, 44 | { 45 | Name: "logout", 46 | Aliases: []string{"lo"}, 47 | Usage: "Close session", 48 | Action: func(c *cli.Context) error { 49 | a := action.Auth{ 50 | Config: *action.NewGlobalConfig( 51 | c.String("api-url"), 52 | c.String("tokenfile"), 53 | ), 54 | } 55 | err := a.AuthLogout() 56 | if err != nil { 57 | return err 58 | } 59 | return nil 60 | }, 61 | }, 62 | }, 63 | } 64 | -------------------------------------------------------------------------------- /pkg/command/folders.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "password-cli/pkg/action" 5 | 6 | "github.com/urfave/cli/v2" 7 | ) 8 | 9 | var FolderCommands = cli.Command{ 10 | Name: "folders", 11 | Aliases: []string{"f"}, 12 | Usage: "Passwork folder section", 13 | Subcommands: []*cli.Command{ 14 | { 15 | Name: "add", 16 | Aliases: []string{"a"}, 17 | Usage: "Create new folder", 18 | Flags: []cli.Flag{ 19 | &cli.StringFlag{ 20 | Name: "name", 21 | Usage: "Folder name", 22 | Required: true, 23 | }, 24 | &cli.StringFlag{ 25 | Name: "vault-id", 26 | Usage: "Vault ID", 27 | Required: true, 28 | }, 29 | }, 30 | Action: func(c *cli.Context) error { 31 | a := action.Folder{ 32 | Name: c.String("name"), 33 | VaultId: c.String("vault-id"), 34 | Config: *action.NewGlobalConfig( 35 | c.String("api-url"), 36 | c.String("tokenfile"), 37 | ), 38 | } 39 | err := a.FolderAdd() 40 | if err != nil { 41 | return err 42 | } 43 | return nil 44 | }, 45 | }, 46 | { 47 | Name: "get", 48 | Aliases: []string{"g"}, 49 | Usage: "Get folder by ID", 50 | Flags: []cli.Flag{ 51 | &cli.StringFlag{ 52 | Name: "id", 53 | Usage: "Folder ID", 54 | Required: true, 55 | }, 56 | }, 57 | Action: func(c *cli.Context) error { 58 | a := action.Folder{ 59 | Config: *action.NewGlobalConfig( 60 | c.String("api-url"), 61 | c.String("tokenfile"), 62 | ), 63 | } 64 | err := a.FolderGet(c.String("id")) 65 | if err != nil { 66 | return err 67 | } 68 | return nil 69 | }, 70 | }, 71 | { 72 | Name: "edit", 73 | Aliases: []string{"e"}, 74 | Usage: "Rename folder by ID", 75 | Flags: []cli.Flag{ 76 | &cli.StringFlag{ 77 | Name: "id", 78 | Usage: "Folder ID", 79 | Required: true, 80 | }, 81 | &cli.StringFlag{ 82 | Name: "name", 83 | Usage: "Folder new name", 84 | Required: true, 85 | }, 86 | }, 87 | Action: func(c *cli.Context) error { 88 | a := action.Folder{ 89 | Name: c.String("name"), 90 | Config: *action.NewGlobalConfig( 91 | c.String("api-url"), 92 | c.String("tokenfile"), 93 | ), 94 | } 95 | err := a.FolderEdit(c.String("id")) 96 | if err != nil { 97 | return err 98 | } 99 | return nil 100 | }, 101 | }, 102 | { 103 | Name: "delete", 104 | Aliases: []string{"d"}, 105 | Usage: "Delete folder by ID", 106 | Flags: []cli.Flag{ 107 | &cli.StringFlag{ 108 | Name: "id", 109 | Usage: "Folder ID", 110 | Required: true, 111 | }, 112 | }, 113 | Action: func(c *cli.Context) error { 114 | a := action.Folder{ 115 | Config: *action.NewGlobalConfig( 116 | c.String("api-url"), 117 | c.String("tokenfile"), 118 | ), 119 | } 120 | err := a.FolderDelete(c.String("id")) 121 | if err != nil { 122 | return err 123 | } 124 | return nil 125 | }, 126 | }, 127 | { 128 | Name: "passwords", 129 | Aliases: []string{"p"}, 130 | Usage: "Get list of passwords in folder", 131 | Flags: []cli.Flag{ 132 | &cli.StringFlag{ 133 | Name: "id", 134 | Usage: "Folder ID", 135 | Required: true, 136 | }, 137 | }, 138 | Action: func(c *cli.Context) error { 139 | a := action.Folder{ 140 | Config: *action.NewGlobalConfig( 141 | c.String("api-url"), 142 | c.String("tokenfile"), 143 | ), 144 | } 145 | err := a.FolderGetPasswords(c.String("id")) 146 | if err != nil { 147 | return err 148 | } 149 | return nil 150 | }, 151 | }, 152 | { 153 | Name: "search", 154 | Aliases: []string{"s"}, 155 | Usage: "Search folders by filter parameters", 156 | Flags: []cli.Flag{ 157 | &cli.StringFlag{ 158 | Name: "query", 159 | Usage: "Query string", 160 | Required: true, 161 | }, 162 | &cli.StringFlag{ 163 | Name: "vault-id", 164 | Usage: "Vault ID", 165 | }, 166 | }, 167 | Action: func(c *cli.Context) error { 168 | a := action.FolderSearchQuery{ 169 | Query: c.String("query"), 170 | VaultId: c.String("vault-id"), 171 | Config: *action.NewGlobalConfig( 172 | c.String("api-url"), 173 | c.String("tokenfile"), 174 | ), 175 | } 176 | err := a.FolderSearch() 177 | if err != nil { 178 | return err 179 | } 180 | return nil 181 | }, 182 | }, 183 | { 184 | Name: "children", 185 | Aliases: []string{"c"}, 186 | Usage: "Get list of children folders", 187 | Flags: []cli.Flag{ 188 | &cli.StringFlag{ 189 | Name: "id", 190 | Usage: "Folder ID", 191 | Required: true, 192 | }, 193 | }, 194 | Action: func(c *cli.Context) error { 195 | a := action.Folder{ 196 | Config: *action.NewGlobalConfig( 197 | c.String("api-url"), 198 | c.String("tokenfile"), 199 | ), 200 | } 201 | err := a.FolderGetChildren(c.String("id")) 202 | if err != nil { 203 | return err 204 | } 205 | return nil 206 | }, 207 | }, 208 | }, 209 | } 210 | -------------------------------------------------------------------------------- /pkg/command/helpers.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "github.com/urfave/cli/v2" 5 | ) 6 | 7 | var Commands = []*cli.Command{ 8 | &AuthCommands, 9 | &VaultCommands, 10 | &PasswordCommands, 11 | &UserCommands, 12 | &FolderCommands, 13 | } 14 | -------------------------------------------------------------------------------- /pkg/command/passwords.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "encoding/base64" 5 | "password-cli/pkg/action" 6 | 7 | "github.com/urfave/cli/v2" 8 | ) 9 | 10 | var PasswordCommands = cli.Command{ 11 | Name: "password", 12 | Aliases: []string{"p"}, 13 | Usage: "Passwork password section", 14 | Subcommands: []*cli.Command{ 15 | { 16 | Name: "add", 17 | Aliases: []string{"a"}, 18 | Usage: "Create new password", 19 | Flags: []cli.Flag{ 20 | &cli.StringFlag{ 21 | Name: "name", 22 | Usage: "Password name", 23 | Required: true, 24 | }, 25 | &cli.StringFlag{ 26 | Name: "login", 27 | Usage: "Password login", 28 | Required: true, 29 | }, 30 | &cli.StringFlag{ 31 | Name: "description", 32 | Usage: "Password description", 33 | }, 34 | &cli.StringFlag{ 35 | Name: "password", 36 | Usage: "Password value", 37 | Required: true, 38 | }, 39 | &cli.StringFlag{ 40 | Name: "vault-id", 41 | Usage: "Password Vault ID", 42 | Required: true, 43 | }, 44 | &cli.StringFlag{ 45 | Name: "folder-id", 46 | Usage: "Password Folder ID", 47 | Required: true, 48 | }, 49 | &cli.StringFlag{ 50 | Name: "url", 51 | Usage: "Password URL", 52 | }, 53 | &cli.StringSliceFlag{ 54 | Name: "tags", 55 | Usage: "Password Tags", 56 | }, 57 | &cli.StringFlag{ 58 | Name: "master-hash", 59 | Usage: "Password Master Hash", 60 | }, 61 | &cli.IntFlag{ 62 | Name: "color", 63 | Usage: "Password Color", 64 | }, 65 | }, 66 | Action: func(c *cli.Context) error { 67 | a := action.Password{ 68 | Name: c.String("name"), 69 | Login: c.String("login"), 70 | CryptedPassword: base64.StdEncoding.EncodeToString([]byte(c.String("password"))), 71 | Description: c.String("description"), 72 | Url: c.String("url"), 73 | FolderId: c.String("folder-id"), 74 | VaultId: c.String("vault-id"), 75 | MasterHash: c.String("master-hash"), 76 | Color: c.Int("color"), 77 | Tags: c.StringSlice("tags"), 78 | Config: *action.NewGlobalConfig( 79 | c.String("api-url"), 80 | c.String("tokenfile"), 81 | ), 82 | } 83 | err := a.PasswordAdd() 84 | if err != nil { 85 | return err 86 | } 87 | return nil 88 | }, 89 | }, 90 | { 91 | Name: "search", 92 | Aliases: []string{"s"}, 93 | Usage: "Search passwords by filter parameters", 94 | Flags: []cli.Flag{ 95 | &cli.StringFlag{ 96 | Name: "query", 97 | Usage: "Query string", 98 | Required: true, 99 | }, 100 | &cli.StringFlag{ 101 | Name: "vault-id", 102 | Usage: "Vault ID", 103 | }, 104 | &cli.IntSliceFlag{ 105 | Name: "colors", 106 | Usage: "Colors int slice", 107 | }, 108 | &cli.StringSliceFlag{ 109 | Name: "tags", 110 | Usage: "Tags string slice", 111 | }, 112 | }, 113 | Action: func(c *cli.Context) error { 114 | a := action.PasswordSearchQuery{ 115 | Query: c.String("query"), 116 | VaultId: c.String("vault-id"), 117 | Colors: c.IntSlice("colors"), 118 | Tags: c.StringSlice("tags"), 119 | Config: *action.NewGlobalConfig( 120 | c.String("api-url"), 121 | c.String("tokenfile"), 122 | ), 123 | } 124 | err := a.PasswordSearch() 125 | if err != nil { 126 | return err 127 | } 128 | return nil 129 | }, 130 | }, 131 | { 132 | Name: "get", 133 | Aliases: []string{"g"}, 134 | Usage: "Get password by ID", 135 | Flags: []cli.Flag{ 136 | &cli.StringFlag{ 137 | Name: "id", 138 | Usage: "Password ID", 139 | Required: true, 140 | }, 141 | }, 142 | Action: func(c *cli.Context) error { 143 | a := action.Password{ 144 | Config: *action.NewGlobalConfig( 145 | c.String("api-url"), 146 | c.String("tokenfile"), 147 | ), 148 | } 149 | err := a.PasswordGet(c.String("id")) 150 | if err != nil { 151 | return err 152 | } 153 | return nil 154 | }, 155 | }, 156 | { 157 | Name: "delete", 158 | Aliases: []string{"d"}, 159 | Usage: "Delete password by ID", 160 | Flags: []cli.Flag{ 161 | &cli.StringFlag{ 162 | Name: "id", 163 | Usage: "Password ID", 164 | Required: true, 165 | }, 166 | }, 167 | Action: func(c *cli.Context) error { 168 | a := action.Password{ 169 | Config: *action.NewGlobalConfig( 170 | c.String("api-url"), 171 | c.String("tokenfile"), 172 | ), 173 | } 174 | err := a.PasswordDelete(c.String("id")) 175 | if err != nil { 176 | return err 177 | } 178 | return nil 179 | }, 180 | }, 181 | { 182 | Name: "edit", 183 | Aliases: []string{"e"}, 184 | Usage: "Edit password by ID", 185 | Flags: []cli.Flag{ 186 | &cli.StringFlag{ 187 | Name: "id", 188 | Usage: "Password ID", 189 | Required: true, 190 | }, 191 | &cli.StringFlag{ 192 | Name: "name", 193 | Usage: "Password name", 194 | }, 195 | &cli.StringFlag{ 196 | Name: "login", 197 | Usage: "Password login", 198 | }, 199 | &cli.StringFlag{ 200 | Name: "description", 201 | Usage: "Password description", 202 | }, 203 | &cli.StringFlag{ 204 | Name: "password", 205 | Usage: "Password value", 206 | }, 207 | &cli.StringFlag{ 208 | Name: "vault-id", 209 | Usage: "Password Vault ID", 210 | }, 211 | &cli.StringFlag{ 212 | Name: "folder-id", 213 | Usage: "Password Folder ID", 214 | }, 215 | &cli.StringFlag{ 216 | Name: "url", 217 | Usage: "Password URL", 218 | }, 219 | &cli.StringSliceFlag{ 220 | Name: "tags", 221 | Usage: "Password Tags", 222 | }, 223 | &cli.StringFlag{ 224 | Name: "master-hash", 225 | Usage: "Password Master Hash", 226 | }, 227 | &cli.IntFlag{ 228 | Name: "color", 229 | Usage: "Password Color", 230 | }, 231 | }, 232 | Action: func(c *cli.Context) error { 233 | a := action.Password{ 234 | Name: c.String("name"), 235 | Login: c.String("login"), 236 | CryptedPassword: base64.StdEncoding.EncodeToString([]byte(c.String("password"))), 237 | Description: c.String("description"), 238 | Url: c.String("url"), 239 | FolderId: c.String("folder-id"), 240 | VaultId: c.String("vault-id"), 241 | MasterHash: c.String("master-hash"), 242 | Color: c.Int("color"), 243 | Tags: c.StringSlice("tags"), 244 | Config: *action.NewGlobalConfig( 245 | c.String("api-url"), 246 | c.String("tokenfile"), 247 | ), 248 | } 249 | err := a.PasswordEdit(c.String("id")) 250 | if err != nil { 251 | return err 252 | } 253 | return nil 254 | }, 255 | }, 256 | { 257 | Name: "copy", 258 | Aliases: []string{"c"}, 259 | Usage: "Copy password to another vault/folder", 260 | Flags: []cli.Flag{ 261 | &cli.StringFlag{ 262 | Name: "id", 263 | Usage: "Password ID", 264 | Required: true, 265 | }, 266 | &cli.StringFlag{ 267 | Name: "to-folder-id", 268 | Usage: "Password destination Folder ID", 269 | Required: true, 270 | }, 271 | &cli.StringFlag{ 272 | Name: "to-vault-id", 273 | Usage: "Password destination Vault ID", 274 | Required: true, 275 | }, 276 | &cli.StringFlag{ 277 | Name: "password", 278 | Usage: "Password value. Will be EMPTY if omitted", 279 | }, 280 | }, 281 | Action: func(c *cli.Context) error { 282 | a := action.Password{ 283 | VaultTo: c.String("to-vault-id"), 284 | FolderTo: c.String("to-folder-id"), 285 | CryptedPassword: base64.StdEncoding.EncodeToString([]byte(c.String("password"))), 286 | Config: *action.NewGlobalConfig( 287 | c.String("api-url"), 288 | c.String("tokenfile"), 289 | ), 290 | } 291 | err := a.PasswordCopy(c.String("id")) 292 | if err != nil { 293 | return err 294 | } 295 | return nil 296 | }, 297 | }, 298 | { 299 | Name: "move", 300 | Aliases: []string{"m"}, 301 | Usage: "Move password to another vault/folder", 302 | Flags: []cli.Flag{ 303 | &cli.StringFlag{ 304 | Name: "id", 305 | Usage: "Password ID", 306 | Required: true, 307 | }, 308 | &cli.StringFlag{ 309 | Name: "to-folder-id", 310 | Usage: "Password destination Folder ID", 311 | Required: true, 312 | }, 313 | &cli.StringFlag{ 314 | Name: "to-vault-id", 315 | Usage: "Password destination Vault ID", 316 | Required: true, 317 | }, 318 | &cli.StringFlag{ 319 | Name: "password", 320 | Usage: "Password value. Will be EMPTY if omitted", 321 | }, 322 | }, 323 | Action: func(c *cli.Context) error { 324 | a := action.Password{ 325 | VaultTo: c.String("to-vault-id"), 326 | FolderTo: c.String("to-folder-id"), 327 | CryptedPassword: base64.StdEncoding.EncodeToString([]byte(c.String("password"))), 328 | Config: *action.NewGlobalConfig( 329 | c.String("api-url"), 330 | c.String("tokenfile"), 331 | ), 332 | } 333 | err := a.PasswordMove(c.String("id")) 334 | if err != nil { 335 | return err 336 | } 337 | return nil 338 | }, 339 | }, 340 | { 341 | Name: "get-recent", 342 | Aliases: []string{"gr"}, 343 | Usage: "Get recently viewed passwords", 344 | Action: func(c *cli.Context) error { 345 | a := action.Password{ 346 | Config: *action.NewGlobalConfig( 347 | c.String("api-url"), 348 | c.String("tokenfile"), 349 | ), 350 | } 351 | err := a.PasswordGetRecent() 352 | if err != nil { 353 | return err 354 | } 355 | return nil 356 | }, 357 | }, 358 | { 359 | Name: "get-favorite", 360 | Aliases: []string{"gf"}, 361 | Usage: "Get favorite passwords", 362 | Action: func(c *cli.Context) error { 363 | a := action.Password{ 364 | Config: *action.NewGlobalConfig( 365 | c.String("api-url"), 366 | c.String("tokenfile"), 367 | ), 368 | } 369 | err := a.PasswordGetFavorite() 370 | if err != nil { 371 | return err 372 | } 373 | return nil 374 | }, 375 | }, 376 | { 377 | Name: "make-favorite", 378 | Aliases: []string{"mf"}, 379 | Usage: "Mark password as favorite", 380 | Flags: []cli.Flag{ 381 | &cli.StringFlag{ 382 | Name: "id", 383 | Usage: "Password ID", 384 | Required: true, 385 | }, 386 | }, 387 | Action: func(c *cli.Context) error { 388 | a := action.Password{ 389 | Config: *action.NewGlobalConfig( 390 | c.String("api-url"), 391 | c.String("tokenfile"), 392 | ), 393 | } 394 | err := a.PasswordMakeFavorite(c.String("id")) 395 | if err != nil { 396 | return err 397 | } 398 | return nil 399 | }, 400 | }, 401 | { 402 | Name: "make-unfavorite", 403 | Aliases: []string{"mu"}, 404 | Usage: "Unfavorite password", 405 | Flags: []cli.Flag{ 406 | &cli.StringFlag{ 407 | Name: "id", 408 | Usage: "Password ID", 409 | Required: true, 410 | }, 411 | }, 412 | Action: func(c *cli.Context) error { 413 | a := action.Password{ 414 | Config: *action.NewGlobalConfig( 415 | c.String("api-url"), 416 | c.String("tokenfile"), 417 | ), 418 | } 419 | err := a.PasswordMakeUnFavorite(c.String("id")) 420 | if err != nil { 421 | return err 422 | } 423 | return nil 424 | }, 425 | }, 426 | { 427 | Name: "search-url", 428 | Aliases: []string{"su"}, 429 | Usage: "Search passwords by full URL ", 430 | Flags: []cli.Flag{ 431 | &cli.StringFlag{ 432 | Name: "url", 433 | Usage: "Password URL", 434 | Required: true, 435 | }, 436 | }, 437 | Action: func(c *cli.Context) error { 438 | a := action.Password{ 439 | Url: c.String("url"), 440 | Config: *action.NewGlobalConfig( 441 | c.String("api-url"), 442 | c.String("tokenfile"), 443 | ), 444 | } 445 | err := a.PasswordSearchByURL() 446 | if err != nil { 447 | return err 448 | } 449 | return nil 450 | }, 451 | }, 452 | }, 453 | } 454 | -------------------------------------------------------------------------------- /pkg/command/user.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "password-cli/pkg/action" 5 | 6 | "github.com/urfave/cli/v2" 7 | ) 8 | 9 | var UserCommands = cli.Command{ 10 | Name: "user", 11 | Aliases: []string{"u"}, 12 | Usage: "Passwork user section", 13 | Subcommands: []*cli.Command{ 14 | { 15 | Name: "get", 16 | Aliases: []string{"g"}, 17 | Usage: "Get user info", 18 | Action: func(c *cli.Context) error { 19 | a := action.User{ 20 | Config: *action.NewGlobalConfig( 21 | c.String("api-url"), 22 | c.String("tokenfile"), 23 | ), 24 | } 25 | err := a.UserGet() 26 | if err != nil { 27 | return err 28 | } 29 | return nil 30 | }, 31 | }, 32 | }, 33 | } 34 | -------------------------------------------------------------------------------- /pkg/command/vaults.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "password-cli/pkg/action" 5 | 6 | "github.com/urfave/cli/v2" 7 | ) 8 | 9 | var VaultCommands = cli.Command{ 10 | Name: "vault", 11 | Aliases: []string{"v"}, 12 | Usage: "Passwork vault section", 13 | Subcommands: []*cli.Command{ 14 | { 15 | Name: "list", 16 | Aliases: []string{"l"}, 17 | Usage: "Get list of user vaults", 18 | Action: func(c *cli.Context) error { 19 | a := action.Vault{ 20 | Config: *action.NewGlobalConfig( 21 | c.String("api-url"), 22 | c.String("tokenfile"), 23 | ), 24 | } 25 | err := a.VaultList() 26 | if err != nil { 27 | return err 28 | } 29 | return nil 30 | }, 31 | }, 32 | { 33 | Name: "all-tags", 34 | Aliases: []string{"at"}, 35 | Usage: "Get all tags", 36 | Action: func(c *cli.Context) error { 37 | a := action.Vault{ 38 | Config: *action.NewGlobalConfig( 39 | c.String("api-url"), 40 | c.String("tokenfile"), 41 | ), 42 | } 43 | err := a.VaultAllTags() 44 | if err != nil { 45 | return err 46 | } 47 | return nil 48 | }, 49 | }, 50 | { 51 | Name: "domain", 52 | Aliases: []string{"d"}, 53 | Usage: "Get domain info", 54 | Action: func(c *cli.Context) error { 55 | a := action.Vault{ 56 | Config: *action.NewGlobalConfig( 57 | c.String("api-url"), 58 | c.String("tokenfile"), 59 | ), 60 | } 61 | err := a.VaultDomain() 62 | if err != nil { 63 | return err 64 | } 65 | return nil 66 | }, 67 | }, 68 | { 69 | Name: "folders", 70 | Aliases: []string{"f"}, 71 | Usage: "Get folders by vault id", 72 | Flags: []cli.Flag{ 73 | &cli.StringFlag{ 74 | Name: "id", 75 | Usage: "Vault ID", 76 | Required: true, 77 | }, 78 | }, 79 | Action: func(c *cli.Context) error { 80 | a := action.Vault{ 81 | Config: *action.NewGlobalConfig( 82 | c.String("api-url"), 83 | c.String("tokenfile"), 84 | ), 85 | } 86 | err := a.VaultGetFolders(c.String("id")) 87 | if err != nil { 88 | return err 89 | } 90 | return nil 91 | }, 92 | }, 93 | { 94 | Name: "passwords", 95 | Aliases: []string{"p"}, 96 | Usage: "Get passwords in vault root", 97 | Flags: []cli.Flag{ 98 | &cli.StringFlag{ 99 | Name: "id", 100 | Usage: "Vault ID", 101 | Required: true, 102 | }, 103 | }, 104 | Action: func(c *cli.Context) error { 105 | a := action.Vault{ 106 | Config: *action.NewGlobalConfig( 107 | c.String("api-url"), 108 | c.String("tokenfile"), 109 | ), 110 | } 111 | err := a.VaultGetPasswords(c.String("id")) 112 | if err != nil { 113 | return err 114 | } 115 | return nil 116 | }, 117 | }, 118 | { 119 | Name: "full-info", 120 | Aliases: []string{"fi"}, 121 | Usage: "Get folders and passwords in vault root", 122 | Flags: []cli.Flag{ 123 | &cli.StringFlag{ 124 | Name: "id", 125 | Usage: "Vault ID", 126 | Required: true, 127 | }, 128 | }, 129 | Action: func(c *cli.Context) error { 130 | a := action.Vault{ 131 | Config: *action.NewGlobalConfig( 132 | c.String("api-url"), 133 | c.String("tokenfile"), 134 | ), 135 | } 136 | err := a.VaultGetFullInfo(c.String("id")) 137 | if err != nil { 138 | return err 139 | } 140 | return nil 141 | }, 142 | }, 143 | { 144 | Name: "tags", 145 | Aliases: []string{"t"}, 146 | Usage: "Get all tags used in vault", 147 | Flags: []cli.Flag{ 148 | &cli.StringFlag{ 149 | Name: "id", 150 | Usage: "Vault ID", 151 | Required: true, 152 | }, 153 | }, 154 | Action: func(c *cli.Context) error { 155 | a := action.Vault{ 156 | Config: *action.NewGlobalConfig( 157 | c.String("api-url"), 158 | c.String("tokenfile"), 159 | ), 160 | } 161 | err := a.VaultGetTags(c.String("id")) 162 | if err != nil { 163 | return err 164 | } 165 | return nil 166 | }, 167 | }, 168 | }, 169 | } 170 | --------------------------------------------------------------------------------