├── .github
└── workflows
│ └── stale-checker.yml
├── .gitignore
├── LICENSE
├── README.md
├── account-management
├── README.md
├── create_vaults_from_input.py
├── remove-permissions-groups-and-vault.py
├── requirements.txt
└── vault-details.sh
├── connect
└── reverse-sharing
│ ├── Dockerfile
│ ├── README.md
│ ├── app.py
│ ├── demo
│ └── reverse-sharing-demo.mp4
│ ├── docker-compose.yml
│ ├── requirements.txt
│ └── templates
│ ├── index.html
│ └── login.html
├── dev-onboarding-guide
├── ssh-checker.sh
└── ssh-guide.md
├── device-trust
└── reporting-db
│ └── app_report.sql
├── item-management
├── README.md
├── bulk-update-by-csv
│ ├── README.md
│ ├── csv_update.py
│ └── sample.txt
├── bulk-update-url-field-by-vault.ps1
├── bulk-update-url-field-by-vault.py
├── create-vault-based-on-item.sh
├── modify-field-type-by-vault.sh
├── recording-item-share.sh
└── requirements.txt
├── migration
├── README.md
├── cleanup-scripts
│ ├── README.md
│ ├── add_vault_prefix.py
│ ├── move_items_to_tracked_vault.py
│ ├── recreate_metadata_entry.py
│ ├── remove_imported_prefix.py
│ └── vault_dedupe_helper.py
└── lastpass
│ ├── README.md
│ ├── folder_migrate.py
│ ├── lastpass-migrate.zip
│ ├── lpass.py
│ ├── main.py
│ ├── template_generator.py
│ ├── template_generator_test.py
│ ├── utils.py
│ ├── utils_test.py
│ └── vault_item_import.py
├── onepassword_sdks
├── README.md
├── demo-inventory-tracker-webapp
│ ├── .env.template
│ ├── Dockerfile
│ ├── README.md
│ ├── app.js
│ ├── docker-compose.yml
│ ├── package-lock.json
│ ├── package.json
│ ├── public
│ │ └── styles.css
│ └── views
│ │ ├── includes
│ │ └── header.ejs
│ │ ├── index.ejs
│ │ ├── item.ejs
│ │ └── items.ejs
├── demo-reverse-sharing
│ ├── .env.template
│ ├── Dockerfile
│ ├── README.md
│ ├── app.py
│ ├── docker-compose.yml
│ ├── requirements.txt
│ └── templates
│ │ └── index.html
├── demo-share-okta-user-script
│ ├── .env.template
│ ├── README.md
│ ├── create-okta-user.py
│ └── requirements.txt
├── demo-share-script
│ ├── README.md
│ ├── files
│ │ ├── README.md
│ │ └── op-dev.png
│ ├── requirements.txt
│ └── sdk_share_script.py
├── demo-vault-backup-webapp
│ ├── Dockerfile
│ ├── README.md
│ ├── docker-compose.yml
│ ├── package.json
│ ├── public
│ │ └── .gitkeep
│ ├── uploads
│ │ └── .gitkeep
│ ├── views
│ │ ├── backup.ejs
│ │ ├── restore.ejs
│ │ └── welcome.ejs
│ └── webapp.js
└── demo-vault-migration-webapp
│ ├── Dockerfile
│ ├── README.md
│ ├── docker-compose.yml
│ ├── package.json
│ ├── public
│ └── styles.css
│ ├── views
│ ├── migration.ejs
│ └── welcome.ejs
│ └── webapp.js
├── reporting
├── README.md
├── beta
│ └── golang-lambda-deployment
│ │ ├── README.md
│ │ ├── bootstrap
│ │ ├── bootstrap.zip
│ │ ├── go.mod
│ │ ├── go.sum
│ │ └── main.go
├── user-access-list.py
├── user-and-item-list.py
├── vault-user-access-report.py
└── vault-user-group-access-report.py
├── scripted-provisioning
├── README.md
├── people.csv
├── provisioning.ps1
└── provisioning.py
└── user-management
├── README.md
├── identify-absentees.ps1
└── identify-absentees.py
/.github/workflows/stale-checker.yml:
--------------------------------------------------------------------------------
1 | # This workflow warns and then closes issues and PRs that have had no activity for a specified amount of time.
2 | #
3 | # You can adjust the behavior by modifying this file.
4 | # For more information, see:
5 | # https://github.com/actions/stale
6 | name: Mark stale issues and pull requests
7 |
8 | on:
9 | schedule:
10 | - cron: "0 4 * * *"
11 | workflow_dispatch:
12 |
13 | jobs:
14 | stale:
15 | runs-on: ubuntu-latest
16 | permissions:
17 | issues: write
18 | pull-requests: write
19 |
20 | steps:
21 | - uses: actions/stale@v5
22 | with:
23 | repo-token: ${{ secrets.GITHUB_TOKEN }}
24 | days-before-stale: 14
25 | stale-issue-label: "stale:needs-attention"
26 | stale-issue-message: >
27 | This issue has been automatically marked as stale because it has not had recent activity.
28 | It will be closed in two weeks if no further activity occurs.
29 | 1Password employees have been nudged.
30 | stale-pr-label: "stale:needs-attention"
31 | stale-pr-message: >
32 | This PR has been automatically marked as stale because it has not had recent activity.
33 | It will be closed in two weeks if no further activity occurs.
34 | 1Password employees have been nudged.
35 | days-before-close: 14
36 | close-issue-message: >
37 | This issue has been automatically closed due to inactivity.
38 | Please re-open if this still requires attention.
39 | close-pr-message: >
40 | This pull request has been automatically closed due to inactivity.
41 | Please re-open if it is still required.
42 | exempt-issue-labels: "keep-alive"
43 | exempt-pr-labels: "keep-alive"
44 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # General
2 | .DS_Store
3 | .AppleDouble
4 | .LSOverride
5 | .csv
6 |
7 | # Icon must end with two \r
8 | Icon
9 |
10 |
11 | # Thumbnails
12 | ._*
13 |
14 | # Files that might appear in the root of a volume
15 | .DocumentRevisions-V100
16 | .fseventsd
17 | .Spotlight-V100
18 | .TemporaryItems
19 | .Trashes
20 | .VolumeIcon.icns
21 | .com.apple.timemachine.donotpresent
22 |
23 | # Directories potentially created on remote AFP share
24 | .AppleDB
25 | .AppleDesktop
26 | Network Trash Folder
27 | Temporary Items
28 | .apdisk
29 | *cursor
30 | *.log
31 | logs
32 |
33 |
34 |
35 | # Node stuff
36 | # Logs
37 | logs
38 | *.log
39 | npm-debug.log*
40 | yarn-debug.log*
41 | yarn-error.log*
42 | lerna-debug.log*
43 | .pnpm-debug.log*
44 |
45 | # Diagnostic reports (https://nodejs.org/api/report.html)
46 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
47 |
48 | # Runtime data
49 | pids
50 | *.pid
51 | *.seed
52 | *.pid.lock
53 |
54 | # Directory for instrumented libs generated by jscoverage/JSCover
55 | lib-cov
56 |
57 | # Coverage directory used by tools like istanbul
58 | coverage
59 | *.lcov
60 |
61 | # nyc test coverage
62 | .nyc_output
63 |
64 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
65 | .grunt
66 |
67 | # Bower dependency directory (https://bower.io/)
68 | bower_components
69 |
70 | # node-waf configuration
71 | .lock-wscript
72 |
73 | # Compiled binary addons (https://nodejs.org/api/addons.html)
74 | build/Release
75 |
76 | # Dependency directories
77 | node_modules
78 | jspm_packages/
79 |
80 | # Snowpack dependency directory (https://snowpack.dev/)
81 | web_modules/
82 |
83 | # TypeScript cache
84 | *.tsbuildinfo
85 |
86 | # Optional npm cache directory
87 | .npm
88 |
89 | # Optional eslint cache
90 | .eslintcache
91 |
92 | # Optional stylelint cache
93 | .stylelintcache
94 |
95 | # Microbundle cache
96 | .rpt2_cache/
97 | .rts2_cache_cjs/
98 | .rts2_cache_es/
99 | .rts2_cache_umd/
100 |
101 | # Optional REPL history
102 | .node_repl_history
103 |
104 | # Output of 'npm pack'
105 | *.tgz
106 |
107 | # Yarn Integrity file
108 | .yarn-integrity
109 |
110 | # dotenv environment variable files
111 | .env
112 | .env.development.local
113 | .env.test.local
114 | .env.production.local
115 | .env.local
116 |
117 | # parcel-bundler cache (https://parceljs.org/)
118 | .cache
119 | .parcel-cache
120 |
121 | # Next.js build output
122 | .next
123 | out
124 |
125 | # Nuxt.js build / generate output
126 | .nuxt
127 | dist
128 |
129 | # Gatsby files
130 | .cache/
131 | # Comment in the public line in if your project uses Gatsby and not Next.js
132 | # https://nextjs.org/blog/next-9-1#public-directory-support
133 | # public
134 |
135 | # vuepress build output
136 | .vuepress/dist
137 |
138 | # vuepress v2.x temp and cache directory
139 | .temp
140 | .cache
141 |
142 | # Docusaurus cache and generated files
143 | .docusaurus
144 |
145 | # Serverless directories
146 | .serverless/
147 |
148 | # FuseBox cache
149 | .fusebox/
150 |
151 | # DynamoDB Local files
152 | .dynamodb/
153 |
154 | # TernJS port file
155 | .tern-port
156 |
157 | # Stores VSCode versions used for testing VSCode extensions
158 | .vscode-test
159 | .vscode/
160 | # yarn v2
161 | .yarn/cache
162 | .yarn/unplugged
163 | .yarn/build-state.yml
164 | .yarn/install-state.gz
165 | .pnp.*
166 | events-api/events-lambda-js-test/.aws-sam/build/template.yaml
167 | events-api/events-lambda-js-test/.aws-sam/build/EventsAPI/buildspec.yml
168 | events-api/events-lambda-js-test/.aws-sam/build/EventsAPI/package-lock.json
169 | events-api/events-lambda-js-test/.aws-sam/build/EventsAPI/package.json
170 | events-api/events-lambda-js-test/.aws-sam/build/EventsAPI/README.md
171 | events-api/events-lambda-js-test/.aws-sam/build/EventsAPI/samconfig.toml
172 | events-api/events-lambda-js-test/.aws-sam/build/EventsAPI/template.yaml
173 | events-api/events-lambda-js-test/.aws-sam/build/EventsAPI/__tests__/unit/handlers/scheduled-event-logger.test.mjs
174 | events-api/events-lambda-js-test/.aws-sam/build/EventsAPI/events/event-cloudwatch-event.json
175 | events-api/events-lambda-js-test/.aws-sam/build/EventsAPI/src/handlers/main.mjs
176 | c-scripts/abk/create_abk_vaults.py
177 | c-scripts/abk/others.txt
178 |
179 | # Task
180 | .task/
181 |
182 | .venv
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 1Password
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Examples, templates, and other goodies from the 1Password Solutions team
2 |
3 |
✨ News!
4 | Introducing new item management with 1Password SDKs!
5 |
6 | * [Read all about the 1Password SDKs](https://developer.1password.com/docs/sdks/)
7 | * [Check out a demo app or script](/onepassword_sdks/)
8 |
9 | ## Introduction
10 |
11 | The 1Password [Command Line Interface](https://developer.1password.com/docs/cli/) (called `op` from this point forward) allows you to manage some aspects of a 1Password account, use secure secrets references to avoid storing secrets as plaintext environment variables, and perform CRUD actions on items you store in 1Password.
12 |
13 | This repository contains example and demo scripts for `op` with the intent of providing people with inspiration or a starting point for their own scripts.
14 |
15 | The scripts here assume you have the `op` [installed and configured](https://developer.1password.com/docs/cli/get-started).
16 |
17 | ## Complete 1Password command line tool documentation
18 |
19 | For full documentation of the 1Password command line interface, please visit [developer.1password.com/docs/cli/](https://developer.1password.com/docs/cli/)
20 |
21 | ## Handy tools
22 |
23 | `jq`, a command line tool with robust JSON support, is an essential tool when using `op`. Many of the provided examples use `jq` and you will need it installed before using any of the examples here. Download `jq` from [the developer](https://stedolan.github.io/jq/).
24 |
25 | ## Note
26 |
27 | Unless otherwise stated, these scripts are not intended to be run in an automated or unattended environment.
28 |
29 | Scripts provided here are not intended to be run as-is. They are intended as examples of how to perform certain tasks. You will need to modify the scripts to fit your exact needs and suite your specific environment.
30 |
31 | ## Contents
32 |
33 | * ✨ **NEW!** [Use 1Password SDKs to perform bulk actions](/onepassword_sdks/)
34 | * [Migrate from another password solution](migration/)
35 | * [Provision new users from a CSV](scripted-provisioning/)
36 | * [Scripts for auditing or managing existing users](user-management/)
37 | * [Scripts for managing your vaults and groups](account-management/)
38 | * [Scripts for creating or updating items in bulk, as well as item-sharing](item-management)
39 |
--------------------------------------------------------------------------------
/account-management/README.md:
--------------------------------------------------------------------------------
1 | # Account management scripts
2 |
3 | ## Introduction
4 |
5 | These examples are intended to demonstrate how the 1Password command line tool can help you gain visibility and insight into your 1Password account.
6 |
7 | ## Script Descriptions
8 |
9 | ### [`create_vaults_from_input.py`](./create_vaults_from_input.py)
10 | This script will create a vault for each vault name in a file passed to the script with the `--file=` flag. Optionally remove your own access to the vault after creating it by running the script with the `--remove-me` flag.
11 |
12 | #### Usage
13 | 1. Install 1Password's CLI tool folowing the directions for your operating system here: https://developer.1password.com/docs/cli/get-started
14 | 2. In the 1Password desktop application enable the integration with the CLI, as documented here: https://developer.1password.com/docs/cli/get-started#step-2-turn-on-the-1password-desktop-app-integration
15 | 3. Open a terminal window and confirm:
16 | 4. the 1Password CLI (called `op`) is installed and working, and the desktop application integration is enabled, by trying `op signin` and signing into the desired 1Password account.
17 | 5. Ensure that you have a version of python installed by running `python --version` or `python3 --version` (ideally you have at least python 3.7).
18 | 6. Download this script to a folder on your computer (e.g., `~/Downloads`).
19 | 7. In your terminal, move to that folder (e.g., `cd ~/Downloads`)
20 | 8. Run the script by executing `python3 create_vaults_from_input.py --file=path/to/input/list`
21 |
22 |
23 | #### Options
24 | By default, when a person creates a vault, they are the "vault creator" and have full permissions for each vault they create. More than likely this will be desirable in your situation.
25 |
26 | However, if you'd like to have your access revoked so that you are not directly assigned to the vault after it's created, run the script using the `--remove-me` flag.
27 | * Note that even if you remove yourself from the vaults, members of the Owners and Administrators groups will still have "Manage Vault" permissions on these vaults, and will be able to manage their own or others' access to them.
28 | * To work around some current rate limiting issues related to permissions commands in the CLI, using the `--remove-me` flag will result in the script taking considerably longer to run (the script has an 11 second delay for each vault when modifying permissions to avoid rate limiting).
29 |
30 | ### [`remove-permissions-groups-and-vaults.py`](./remove-permissions-groups-and-vault.py)
31 |
32 | #### Description
33 | When run by a member of the Owners group, this script will remove permissions for every vault that any group you select has access to.
34 |
35 | When run by a non-Owner, this script will remove your selected permissions on vaults that the person running the script also has the `manage vault` permissions for.
36 |
37 | #### Requirements
38 | The Python script requires the `tqdm` package for progress bar functionality. Install it by running:
39 | ```
40 | pip install -r requirements.txt
41 | ```
42 |
43 |
44 | ### [`vault-details.sh`](vault-details.sh)
45 |
46 | When run by a member of the Owners group, this script provides the vault name, the number of items in the vault, the last time the vault contents were updated, and list which users and groups have access to that vault along with their permissions.
47 |
48 | When run by a non-Owner, it will provide these details for all vaults the user running the script has access to.
--------------------------------------------------------------------------------
/account-management/create_vaults_from_input.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 | import argparse
3 | import csv
4 | import json
5 | import subprocess
6 | import sys
7 | import time
8 |
9 | parser = argparse.ArgumentParser(
10 | "Create a 1Password vault for each name in a list of vault names provided as a plaintext file using the --file flag.",
11 | "By default, you will have full permissions on each vault created by this script."
12 | "User the --remove-me flag if you'd like to revoke your own access to the vault after it is created. Members of the Owners and Administrators groups will still be able to manage this vault.",
13 | )
14 | parser.add_argument(
15 | "--file",
16 | action="store",
17 | dest="filepath",
18 | help="Specify the path to a CSV file containing the required input data.",
19 | required=True,
20 | )
21 |
22 | parser.add_argument(
23 | "--remove-me",
24 | action="store_true",
25 | dest="removeMe",
26 | help="Remove yourself from the vaults created by this script. To avoid rate limiting, this aggressively throttles the script and each vault will take 11 seconds to process.",
27 | )
28 |
29 | args = parser.parse_args()
30 |
31 |
32 | # Get the User UUID of the person running the script. This is required for other parts of the script.
33 | def getMyUUID() -> str:
34 | print("Ensuring you're signed into 1Password and obtaining your User ID.\n")
35 | r = subprocess.run(["op", "whoami", "--format=json"], capture_output=True)
36 |
37 | # Catch error and kill process
38 | if r.returncode != 0:
39 | sys.exit(
40 | f"🔴 Unable to get your user UUID. Make sure you are are signed into the 1Password CLI. Error: {r.stderr.decode('utf-8')}"
41 | )
42 | print(f"🟢 Obtained your User ID: {json.loads(r.stdout)['user_uuid']} \n")
43 | return json.loads(r.stdout)["user_uuid"]
44 |
45 |
46 | # Grants the group access to the corrosponding vault with defined permissions.
47 | def createVaultWithName(vault: str):
48 | retries = 0
49 | maxRetries = 3
50 | print(f"\t⌛ Attempting to create vault called {vault}.")
51 | while retries < maxRetries:
52 | r = subprocess.run(
53 | [
54 | "op",
55 | "vault",
56 | "create",
57 | vault,
58 | ],
59 | capture_output=True,
60 | )
61 | # time.sleep(11)
62 | # Handle rate limit error
63 | if "rate-limited" in r.stderr.decode("utf-8"):
64 | # Retry after waiting for 60 seconds
65 | print(r.stderr.decode("utf-8"))
66 | print("💤 Sleeping for 10 minutes, go grab a coffee.")
67 | time.sleep(600)
68 | retries += 1
69 | # Catch error but continue
70 | elif r.returncode != 0 and "rate-limited" not in r.stderr.decode("utf-8"):
71 | print(
72 | f"\t🔴 Unable to create vault named '{vault}'. Error: ",
73 | r.stderr.decode("utf-8"),
74 | )
75 | break
76 | else:
77 | print(f"\t🟢 Successfully created '{vault}'")
78 | break
79 |
80 |
81 | # Revokes vault access for the person running the script.
82 | def removeCreatorPermissionsFor(vault: str, userID: str):
83 | retries = 0
84 | maxRetries = 3
85 | print(f"\t⌛ Attempting to remove your access to the newly created vault {vault}.")
86 | while retries < maxRetries:
87 | r = subprocess.run(
88 | ["op", "vault", "user", "revoke", f"--user={userID}", f"--vault={vault}"],
89 | capture_output=True,
90 | )
91 | time.sleep(11)
92 | # Handle rate limit error
93 | if "rate-limited" in r.stderr.decode("utf-8"):
94 | # Retry after waiting for 60 seconds
95 | print(r.stderr.decode("utf-8"))
96 | print("💤 Sleeping for 10 minutes, go grab a coffee.")
97 | time.sleep(600)
98 | retries += 1
99 | # Catch error but continue
100 | elif r.returncode != 0 and "rate-limited" not in r.stderr.decode("utf-8"):
101 | print(
102 | f"\t🔴 There was an issue removing your access to the vault {vault}. Error: ",
103 | r.stderr.decode("utf-8"),
104 | )
105 | return
106 | print(f"\t🟢 Succeeded in removing your access to vault {vault}.\n\n")
107 | return
108 |
109 |
110 | def main():
111 | myUUID: str = getMyUUID()
112 | # Open the csv passed via the --file flag
113 | with open(args.filepath, "r", newline="", encoding="utf-8") as inputFile:
114 | csvReader = csv.reader(inputFile, skipinitialspace=True)
115 | for row in csvReader:
116 | vault: str = row[0].strip()
117 | createVaultWithName(vault)
118 |
119 | # If --remove-me flag was used, remove the script-runner's permission
120 | if args.removeMe:
121 | removeCreatorPermissionsFor(vault, myUUID)
122 |
123 |
124 | main()
125 |
--------------------------------------------------------------------------------
/account-management/requirements.txt:
--------------------------------------------------------------------------------
1 | tqdm
--------------------------------------------------------------------------------
/account-management/vault-details.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | # This script will loop through all vaults the currently-logged-in user has access to.
3 | # For each vault, it will provide the vault name, the number of items in the vault
4 | # the last time the vault contents were updated, and list which users and groups have access
5 | # to the vault along with their permissions.
6 | op signin
7 | for vault in $(op vault list --format=json | jq --raw-output '.[] .id')
8 | do
9 | echo ""
10 | echo "Vault Details"
11 | op vault get $vault --format=json | jq -r '.|{name, items, updated_at}'
12 | sleep 1
13 | echo ""
14 | echo "Users"
15 | op vault user list $vault
16 | sleep 1
17 | echo ""
18 | echo "Groups"
19 | op vault group list $vault
20 | sleep 1
21 | echo ""
22 | echo "End of Vault Details"
23 | sleep 2
24 | clear
25 | echo ""
26 | echo ""
27 | done
--------------------------------------------------------------------------------
/connect/reverse-sharing/Dockerfile:
--------------------------------------------------------------------------------
1 | # Use an official Python runtime as a parent image
2 | FROM python:3.13-alpine
3 |
4 | # Set the working directory in the container
5 | WORKDIR /app
6 |
7 | # Copy the dependencies file to the working directory
8 | COPY requirements.txt .
9 |
10 | # Install any needed packages specified in requirements.txt
11 | # --no-cache-dir reduces image size
12 | # --trusted-host pypi.python.org can sometimes help bypass network issues in restricted environments
13 | RUN pip install --no-cache-dir --trusted-host pypi.python.org -r requirements.txt
14 |
15 | # Copy the current directory contents into the container at /app
16 | COPY . .
17 |
18 | # Make port 5000 available to the world outside this container
19 | # Note: Gunicorn will bind to this port. Flask dev server also uses it by default.
20 | EXPOSE 5000
21 |
22 | # Define environment variables (placeholders, should be set during 'docker run')
23 | # These are just examples, DO NOT HARDCODE secrets here.
24 | # ENV OP_VAULT_UUID=your_vault_id
25 | # ENV FLASK_SECRET_KEY=set_a_strong_secret_key_here
26 |
27 | # Run app.py using gunicorn when the container launches
28 | # Gunicorn is a production-ready WSGI server.
29 | # --bind 0.0.0.0:5000 makes the app accessible from outside the container.
30 | # app:app refers to the Flask application instance 'app' found in the file 'app.py'.
31 | CMD ["gunicorn", "--bind", "0.0.0.0:5000", "app:app"]
--------------------------------------------------------------------------------
/connect/reverse-sharing/README.md:
--------------------------------------------------------------------------------
1 | # Basic 1Password Connect Item Creator Webapp
2 |
3 | A very simple web application built with Flask and deployable via Docker. It provides a basic web form to create new Login items in a specified 1Password vault using the 1Password Connect REST API. **Note:** This version is a proof of concept that can be deployed locally. For production use, look at our [connect documentation](https://developer.1password.com/docs/connect/get-started/).
4 |
5 | ## Features
6 |
7 | - **Create 1Password Items:** Simple web form to input Title, Username, Password, and Notes for new Login items.
8 | - **1Password Connect Integration:** Interacts directly with your 1Password Connect server API.
9 | - **Web Interface:** Minimal interface built with Flask.
10 | - **Dockerized:** Easy deployment using Docker.
11 | - **Configuration via Environment:** Essential 1Password Connect settings are managed through environment variables.
12 |
13 | ## 1Password Setup
14 |
15 | 1. Create a vault in 1Password exclusively for this application and note the UUID.
16 | 2. In 1Password admin console, go to Developer -> Connect servers and create a new connect server with `Read, Write` permissions to that vault. Save the 1password-credentials.json file and place in this directory.
17 | 3. Create an access token with only `write` permissions to that vault.
18 | 4. Copy the token and save it securely. You will need it to create items in the vault.
19 |
20 | ## Prerequisites
21 |
22 | Before you begin, ensure you have the following installed and configured:
23 |
24 | 1. **Docker Engine:** [Install Docker](https://docs.docker.com/engine/install/)
25 | 2. **Environment Variables:** Set up the following environment variables in a `.env` file in this directory:
26 | - `OP_VAULT_UUID`: The UUID of the target 1Password vault.
27 | - `FLASK_SECRET_KEY`: (Recommended) A secret key for Flask, needed for features like flashing messages. While not strictly required for _this specific basic version's core function_, it's good practice to include it. Generate a random string (e.g., using `openssl rand -hex 32`).
28 |
29 | ## Project Structure
30 |
31 | Ensure you have the following files in your project directory:
32 |
33 | ```
34 | ├── app.py # Flask application logic
35 | ├── Dockerfile # Docker build instructions
36 | ├── docker-compose.yml # Docker Compose file for easier management
37 | ├── requirements.txt # Python dependencies
38 | ├── README.md # This file
39 | ├── 1password-credentials.json # Credentials file for 1Password Connect
40 | ├── .env file # Environment variables for Docker Compose
41 | └── templates/
42 | └── index.html # HTML form template for creating items
43 | └── login.html # HTML form template for entering bearer token
44 | ```
45 |
46 | ## Running the Application
47 |
48 | 1. **Navigate:** Open a terminal or command prompt and navigate to the project directory containing the `Dockerfile` and application files.
49 | 2. **Build and run the Docker Image, tailing the logs:**
50 |
51 | ```bash
52 | docker compose up -d --build && docker compose logs -f webapp
53 | ```
54 |
55 | 3. **Access the Application:** Open your web browser and navigate to `http://localhost:5001`. Enter the bearer token with write access to the target vault (the API token for your 1Password Connect server) in the form.
56 | 4. **Create Item:** Fill in the Title (required), Username, Password, and Notes fields for the new 1Password Login item.
57 | 5. **Submit:** Click "Create Item". You will see a success or error message displayed on the page.
58 | 6. **Check 1Password:** Log in to your 1Password account and check the specified vault to see the newly created item.
59 |
--------------------------------------------------------------------------------
/connect/reverse-sharing/demo/reverse-sharing-demo.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1Password/solutions/51d749b42523f5c4d8213696302d0183369a9ea7/connect/reverse-sharing/demo/reverse-sharing-demo.mp4
--------------------------------------------------------------------------------
/connect/reverse-sharing/docker-compose.yml:
--------------------------------------------------------------------------------
1 | services:
2 | # Define the web application service
3 | webapp:
4 | # Build the Docker image using the Dockerfile in the current directory (.)
5 | build: .
6 | networks: [op-connect]
7 | depends_on: [api, sync]
8 | # Name the container for easier identification
9 | container_name: op_connect_webapp_service
10 | # Map port 5001 on the host machine to port 5000 inside the container
11 | # This makes the web app accessible via http://localhost:5001
12 | ports:
13 | - "5001:5000"
14 | # Define environment variables needed by the Flask application (app.py)
15 | # It's highly recommended to use a .env file for secrets
16 | environment:
17 | # These variables will be read from a .env file in the same directory
18 | # or from your host machine's environment variables if the .env file is missing/incomplete
19 | - OP_CONNECT_HOST=http://api:8080 # The host and port of the Connect API
20 | - OP_VAULT_UUID=${OP_VAULT_UUID}
21 | - FLASK_SECRET_KEY=${FLASK_SECRET_KEY} # Important for Flask sessions/flashing
22 | # Optional: Set Flask environment to production (disables debug mode)
23 | # - FLASK_ENV=production
24 | # Define the restart policy for the container
25 | # 'unless-stopped': restarts the container unless it was explicitly stopped
26 | restart: unless-stopped
27 | # Optional: Mount local code for development (uncomment if needed)
28 | # This allows code changes to be reflected without rebuilding the image
29 | # volumes:
30 | # - .:/app
31 | api:
32 | image: 1password/connect-api
33 | networks: [op-connect]
34 | deploy:
35 | replicas: 1
36 | restart_policy:
37 | condition: any
38 | secrets:
39 | - source: credentials
40 | target: /home/opuser/.op/1password-credentials.json
41 | uid: "999"
42 | gid: "999"
43 | mode: 0400
44 | volumes:
45 | - data:/home/opuser/.op/data
46 | user: "999:999"
47 |
48 | sync:
49 | image: 1password/connect-sync
50 | networks: [op-connect]
51 | deploy:
52 | replicas: 1
53 | restart_policy:
54 | condition: any
55 | secrets:
56 | - source: credentials
57 | target: /home/opuser/.op/1password-credentials.json
58 | uid: "999"
59 | gid: "999"
60 | mode: 0400
61 | volumes:
62 | - data:/home/opuser/.op/data
63 | user: "999:999"
64 |
65 | volumes:
66 | data:
67 |
68 | secrets:
69 | credentials:
70 | file: 1password-credentials.json
71 | name: credentials
72 |
73 | networks:
74 | op-connect:
75 | # Note: No external networks or volumes are defined as this is a simple single-service setup
76 | # relying on environment variables for configuration.
77 |
--------------------------------------------------------------------------------
/connect/reverse-sharing/requirements.txt:
--------------------------------------------------------------------------------
1 | Flask>=2.0
2 | requests>=2.20
3 | gunicorn>=20.0 # Recommended for running Flask in Docker
--------------------------------------------------------------------------------
/connect/reverse-sharing/templates/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Create 1Password Item
7 |
88 |
89 |
90 |
93 |
94 |
95 |
--------------------------------------------------------------------------------
/dev-onboarding-guide/ssh-checker.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | echo "1Password SSH Setup Checker"
4 | echo "------------------------------------"
5 |
6 | # Color definitions (optional)
7 | GREEN='\033[0;32m'
8 | RED='\033[0;31m'
9 | YELLOW='\033[1;33m'
10 | NC='\033[0m' # No Color
11 |
12 | # Helper function: display check status
13 | print_status() {
14 | if [ "$2" = "OK" ]; then
15 | echo -e "[${GREEN}OK${NC}] $1"
16 | elif [ "$2" = "FAIL" ]; then
17 | echo -e "[${RED}FAIL${NC}] $1"
18 | elif [ "$2" = "WARN" ]; then
19 | echo -e "[${YELLOW}WARN${NC}] $1"
20 | else
21 | echo -e "[INFO] $1"
22 | fi
23 | }
24 |
25 | # 1. Check SSH_AUTH_SOCK environment variable
26 | echo
27 | print_status "Checking SSH_AUTH_SOCK environment variable..."
28 | if [ -n "$SSH_AUTH_SOCK" ]; then
29 | print_status "SSH_AUTH_SOCK is set: $SSH_AUTH_SOCK" "OK"
30 | if [[ "$SSH_AUTH_SOCK" == *1Password* || "$SSH_AUTH_SOCK" == *1password* ]]; then
31 | print_status "SSH_AUTH_SOCK appears to point to a 1Password agent." "OK"
32 | else
33 | print_status "SSH_AUTH_SOCK may not point to a 1Password agent." "WARN"
34 | echo " Current value: $SSH_AUTH_SOCK"
35 | echo " Please ensure the SSH agent is enabled in 1Password desktop settings,"
36 | echo " restart your terminal, or check your shell configuration file (~/.zshrc, ~/.bash_profile, etc.)."
37 | fi
38 | else
39 | print_status "SSH_AUTH_SOCK is not set." "FAIL"
40 | echo " Please ensure the SSH agent is enabled in 1Password desktop settings,"
41 | echo " restart your terminal, or check your shell configuration file."
42 | fi
43 |
44 | # 2. Check if keys are loaded in the SSH agent (ssh-add -L)
45 | echo
46 | print_status "Checking for keys loaded in the SSH agent (ssh-add -L)..."
47 | # ssh-add -L can return an error code if the agent isn't running, so capture output
48 | agent_keys_output=$(ssh-add -L 2>&1)
49 | agent_keys_exit_code=$?
50 |
51 | if [ $agent_keys_exit_code -eq 0 ]; then
52 | if [[ "$agent_keys_output" == *"1Password"* || "$agent_keys_output" == *"1password"* || "$agent_keys_output" == *"//"* ]]; then # Common 1P key comment format
53 | print_status "1Password managed keys were listed from the SSH agent." "OK"
54 | echo " Listed keys:"
55 | echo -e " ${agent_keys_output}"
56 | elif [[ "$agent_keys_output" == "The agent has no identities." ]]; then
57 | print_status "No keys are loaded in the SSH agent." "WARN"
58 | echo " Ensure you have generated/imported SSH keys in 1Password and they are configured for use with the agent."
59 | echo " Alternatively, if your 1Password app is locked, try unlocking it."
60 | else
61 | print_status "Keys were listed from the SSH agent, but it's unclear if they are from 1Password." "WARN"
62 | echo " Listed keys:"
63 | echo -e " ${agent_keys_output}"
64 | echo " Please verify these are the keys you manage with 1Password."
65 | fi
66 | elif [ $agent_keys_exit_code -eq 2 ]; then # Error code for "Could not open a connection to your authentication agent."
67 | print_status "Could not connect to SSH agent. Is it running? (ssh-add -L returned error)" "FAIL"
68 | echo " Check your SSH_AUTH_SOCK setting and the status of the 1Password SSH agent."
69 | else # Other errors
70 | print_status "Error running ssh-add -L (Code: $agent_keys_exit_code)." "FAIL"
71 | echo " Error output: $agent_keys_output"
72 | fi
73 |
74 | # 3. Test SSH connection to GitHub (optional)
75 | echo
76 | print_status "Testing SSH connection to GitHub (ssh -T git@github.com)..."
77 | echo " This test will attempt to authenticate to GitHub."
78 | echo " Observe if 1Password prompts for biometrics or your system password."
79 | echo " A successful message often looks like 'Hi username! You've successfully authenticated...'"
80 | echo " (Do you want to run this test? [y/N])"
81 | read -r run_github_test
82 |
83 | if [[ "$run_github_test" =~ ^[Yy]$ ]]; then
84 | echo " Running: ssh -T git@github.com ..."
85 | ssh -T git@github.com
86 | echo " Test complete. Check the output above and any 1Password prompts."
87 | else
88 | print_status "GitHub connection test was skipped."
89 | fi
90 |
91 | echo
92 | echo "------------------------------------"
93 | echo "Check complete. If you encounter any issues, please refer to the onboarding guide"
94 | echo "or consult your team lead."
95 |
--------------------------------------------------------------------------------
/device-trust/reporting-db/app_report.sql:
--------------------------------------------------------------------------------
1 | -- "Mac Apps Report"
2 | -- Internal-1P only https://app.kolide.com/4918/reporting/queries/2080
3 |
4 | -- Reporting DB query to retrieve all mac_apps installed across the fleet,
5 | -- filtering out a list of "approved apps" such as 1Password and anything
6 | -- built by either Apple or Google using their bundle_identifier.
7 |
8 | -- The final report contains an ordered list of "unapproved" apps with a
9 | -- JSON formatted device table containing the device name, serial and admin URL.
10 |
11 | WITH device_info AS (
12 | SELECT
13 | id as device_id,
14 | name,
15 | serial,
16 | k2_url,
17 | id || ' (' || name || ')' as device_name
18 | FROM
19 | devices
20 | ),
21 |
22 | apps AS (
23 | SELECT
24 | *
25 | FROM
26 | mac_apps
27 | WHERE
28 | 1=1
29 | AND path LIKE '/Applications%'
30 | AND name NOT LIKE '1Password%.app'
31 | AND bundle_identifier NOT LIKE 'com.apple.%'
32 | AND bundle_identifier NOT LIKE 'com.google.%'
33 | )
34 |
35 | SELECT
36 | a.name,
37 | a.bundle_identifier,
38 | COUNT(*) as count,
39 | JSON_AGG(
40 | JSON_BUILD_OBJECT(
41 | 'device_name', d.device_name,
42 | 'device_serial', d.serial,
43 | 'url', d.k2_url
44 | ) ORDER BY d.device_name
45 | ) as device_table
46 | FROM apps as a
47 | JOIN device_info as d on d.device_id = a.device_id
48 | GROUP BY 1, 2
49 | ORDER BY count DESC
--------------------------------------------------------------------------------
/item-management/README.md:
--------------------------------------------------------------------------------
1 | # Item management scripts
2 |
3 | ## Introduction
4 |
5 | These scripts are examples of how to perform bulk creation, read, update, or delete actions on items in 1Password. As well as an example script to create a temporary 1Password item to use for item sharing.
6 |
7 | For more information about managing items with the 1Password command line tool, see the complete documentation for [op item subcommands](https://developer.1password.com/docs/cli/reference/management-commands/item).
8 |
9 | ## Use-cases
10 |
11 |
12 | ### Bulk selectors
13 |
14 | * You can select items based on several criteria, including the the vault they are stored in, one or more tags, or the value of a field, such as website or username.
15 | * Examples of performing bulk actions on every item in a vault:
16 | * [bulk-update-url-field-by-vault.py](bulk-update-url-field-by-vault.py) or [bulk-update-url-field-by-vault.ps1](bulk-update-url-field-by-vault.ps1)
17 | * [modify-field-type-by-vault.sh](modify-field-type-by-vault.sh)
18 | * Examples of performing bulk actions on all items with a specific tag:
19 |
20 | For more details on how to select items, see [op item list](https://developer.1password.com/docs/cli/reference/management-commands/item#item-list).
21 |
22 | ### Modify the website value of multiple items
23 |
24 | **Requirements**: The Python script requires the `tqdm` package for progress bar functionality. Install it by running:
25 | ```
26 | pip install -r requirements.txt
27 | ```
28 |
29 | If you need to make the same change to the website field of many items, scripts that provide this functionality in both Python and Powershell are available:
30 |
31 | * Python: [bulk-update-url-field-by-vault.py](bulk-update-url-field-by-vault.py)
32 | * Powershell [bulk-update-url-field-by-vault.ps1](bulk-update-url-field-by-vault.ps1).
33 |
34 | ### Change field type while retaining value
35 |
36 | If you have multiple items with incorrect field types but the correct value (e.g., the field is of type `text` but the value is a password, PIN, or other secret), which may happen when importing customized items from outside 1Password, [modify-field-type-by-vault.sh](modify-field-type-by-vault.sh) provides an example of how to convert multiple fields of type `text` to type `password` without changing the value of those fields.
37 |
38 | ### Share Zoom Recording Link using 1Password Item Share
39 |
40 | If you have a Zoom Recording Link that you are looking to share, this script will create a placeholder 1Password Login Item, based off some of the information promoted for. It takes in a standard Zoom Recording link and seperates the two lines out into the URL and Password, to add to the 1Password item accordingly. Lastly it creates a [item-share link](https://developer.1password.com/docs/cli/reference/management-commands/item#item-share) to send out to the provided email addresses. [recording-item-share.sh](recording-item-share.sh) provides an example of how you can utilize the 1Password Item Share to distribute temporary credentials to contacts outside your 1Password account in a secure fashion.
41 |
42 | ### Create Vault based on item content
43 |
44 | Through this script, [create-vault-based-on-item.sh](create-vault-based-on-item.sh), items in a certain vault are checked, and a new vault is created based of a field in the item. From there the original item is recreated in the new vault and removed from the "landing" vault. The concent behind this, is that perhaps 1Password Connect is in place that is tied to a "landing" vault where new items are being created through a web form and Secrets Automation (this part is not part of the script).
45 |
--------------------------------------------------------------------------------
/item-management/bulk-update-by-csv/README.md:
--------------------------------------------------------------------------------
1 | # 1Password Item Updater Script
2 |
3 | This Python script automates the process of updating passwords for items stored in your 1Password vaults using the 1Password Command Line Interface (op). It reads item details and new passwords from a CSV file and applies the changes.
4 |
5 | ## Features
6 |
7 | - Updates passwords for specified 1Password items
8 | - Reads input data from a CSV file
9 | - Identifies the correct password field to update (prioritizes primary password fields, then generic concealed fields)
10 | - Uses the official 1Password CLI (op) for secure interaction with your vaults
11 | - Provides console output for progress and error reporting
12 |
13 | ## Prerequisites
14 |
15 | - **1Password Account**: You need an active 1Password account
16 | - **1Password CLI (op)**: The 1Password Command Line Interface must be installed and configured on your system
17 | - **Signed into op CLI**: You must be signed into your 1Password account via the op CLI. You can do this by running `op signin` and following the prompts. If you use biometric unlock, ensure it's enabled for the CLI
18 | - **Python 3**: Python 3.6 or newer is required to run the script
19 | - **CSV File**: A CSV file containing the items to update, formatted as described below
20 |
21 | ## CSV File Format
22 |
23 | The script expects a CSV file with the following exact column headers:
24 |
25 | - `vault`: The name of the 1Password vault containing the item
26 | - `item title`: The exact title of the item in 1Password
27 | - `new password`: The new password to set for the item
28 |
29 | ### Example CSV (passwords_to_update.csv)
30 |
31 | ```bash
32 | vault,item title,new password
33 | Personal,My Website Login,aVeryStrongNewPassword123!
34 | Work,Server SSH Key,anotherSecureP@ssw0rd
35 | Shared Vault,Database Credentials,"complex_p@$$wOrd_with_commas,and_quotes"
36 | ```
37 |
38 | ### Notes on CSV format
39 |
40 | - The header row is mandatory and must match the names above
41 | - If a new password (or any other field) contains a comma, enclose the entire field value in double quotes (`"`)
42 |
43 | ## Installation & Setup
44 |
45 | 1. Download the Script: Save the Python script (e.g., `update_op_passwords.py`) to your local machine
46 | 2. Make it Executable (Optional but Recommended for Linux/macOS):
47 |
48 | ```bash
49 | chmod +x update_op_passwords.py
50 | ```
51 |
52 | 3. Prepare your CSV file as described in the "CSV File Format" section
53 |
54 | ## Usage
55 |
56 | Run the script from your terminal, providing the path to your CSV file as a command-line argument:
57 |
58 | Using python3:
59 |
60 | ```bash
61 | python3 update_op_passwords.py /path/to/your/passwords_to_update.csv
62 | ```
63 |
64 | If you made it executable (Linux/macOS):
65 |
66 | ```bash
67 | ./update_op_passwords.py /path/to/your/passwords_to_update.csv
68 | ```
69 |
70 | The script will then:
71 |
72 | 1. Verify it can connect to the op CLI
73 | 2. Read each row from your CSV file
74 | 3. For each item:
75 | - Fetch the item details from the specified vault
76 | - Identify the appropriate concealed field (password field)
77 | - Update the field with the new password from the CSV
78 | 4. Print progress messages or any errors encountered to the console
79 |
80 | ## How it Identifies the Password Field
81 |
82 | The script attempts to find the correct field to update using the following logic:
83 |
84 | 1. It looks for a field explicitly marked with `purpose: "PASSWORD"` in the item's JSON structure (this is common for Login items)
85 | 2. If no such field is found, it looks for the first field with `type: "CONCEALED"`
86 | 3. It constructs the necessary field designator for the `op item edit` command, including the section name if the field is part of a section (e.g., `Section Name.Field Label` or just `Field Label`)
87 |
88 | If you need to update a specific custom concealed field when multiple exist and none are marked as the primary password, this script might need modification.
89 |
90 | ## Important Considerations & Warnings
91 |
92 | - **BACKUP YOUR VAULT DATA**: Before running any script that modifies your 1Password data, ensure you have a reliable backup or are comfortable with the changes. Use this script at your own risk.
93 | - **Test Carefully**: It's highly recommended to test the script with a single, non-critical item first to ensure it behaves as expected in your environment.
94 | - **Security of CSV File**: The new password column in your CSV file contains sensitive information in plaintext.
95 | - Store this file securely
96 | - Restrict access to it
97 | - Consider deleting the file or at least the new password column after the update process is successfully completed and verified
98 | - **Error Handling**: The script includes basic error handling. Pay close attention to any error messages printed in the console, as they can help diagnose issues (e.g., item not found, incorrect vault name, op CLI problems).
99 | - **Rate Limiting**: While generally not an issue for typical CLI usage, updating an extremely large number of items very rapidly could potentially encounter rate limits from the 1Password service.
100 | - **Exact Item Titles**: The script relies on exact matches for "item title". Ensure these are correct in your CSV.
101 |
102 | ## Troubleshooting
103 |
104 | - **'op' command not found**: Ensure the 1Password CLI is installed and its location is in your system's PATH environment variable.
105 | - **Error: 1Password CLI 'op account list' command failed**: You are likely not signed into the op CLI. Run `op signin` and complete the authentication process.
106 | - **Item not found / Vault not found**: Double-check the vault and item title entries in your CSV file for typos or case sensitivity issues. Vault and item names must be exact.
107 | - **Could not find a suitable concealed field**: The item specified might not have any fields of type "CONCEALED", or the script could not identify one. Check the item structure in 1Password.
108 |
109 | ## Disclaimer
110 |
111 | This script is provided as-is, without any warranty. Always exercise caution when automating actions on sensitive data.
112 |
--------------------------------------------------------------------------------
/item-management/bulk-update-by-csv/sample.txt:
--------------------------------------------------------------------------------
1 | vault,item title,new password
2 | Personal,My Website Login,aVeryStrongNewPassword123!
3 | Work,Server SSH Key,anotherSecureP@ssw0rd
4 | Shared Vault,Database Credentials,"complex_p@$$wOrd_with_commas,and_quotes"
--------------------------------------------------------------------------------
/item-management/create-vault-based-on-item.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | # This script will loop through vault specied in the vaultUUID variable.
3 | # For each item in this vault, it will create (without administrator group management permissions)
4 | # a new vault based off the name in the fieldvalue variable.
5 | # This could be a custom field such as "Company" in all the items in the vault being looped through.
6 | # The original item, is then created in the new vault and removed from the original vault.
7 |
8 | # This script was inspired by a use case where 1Password Connect is tied to a "landing" vault where new items
9 | # are being created through a web form for new clients/companies in this landing vault, where the new items
10 | # are only stored until they are moved to a specific vault for the client/company.
11 |
12 | #Change this value to the vault that you are moving items from, your landing vault.
13 | vaultUUID=
14 |
15 | #Change the value of this to match the corresponding field you are looking in the item in this landing vault.
16 | fieldvalue=Company
17 |
18 | # creates new vault based of item detail from field from variable `fieldvalue` specified above.
19 | # The field must be valid for the specified vault.
20 | for item in $(op item list --vault $vaultUUID --format=json | jq --raw-output '.[].id')
21 | do
22 | vaultname=`op item get $item --fields $fieldvalue`
23 | op vault create $vaultname --allow-admins-to-manage false
24 | op item get $item --format json | op item create --vault $vaultname
25 | op item delete $item --archive --vault $vaultUUID
26 | done
--------------------------------------------------------------------------------
/item-management/modify-field-type-by-vault.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | #########################################################
3 | # MODIFY FIELD TYPE WHILE RETAINING VALUE
4 | #########################################################
5 | # This example shows how to change the type of a field (e.g., from
6 | # "text" to "password") without changing the value of that field.
7 | # This script assumes that you are modifying multiple items that have the same
8 | # structure. In this case, this script assumes:
9 | # - You are changing "text" field types to "password" field types
10 | # - You are adjusting two top-level field called "Test field 1"
11 | # - You are adjusting four fields in a section called "Example"
12 | # - You can use the following example to generate 10 items with this
13 | # exact structure so you have sample data to experiment with.
14 | # ==============================================================================
15 | # GENERATE SAMPLE DATA
16 | # ==============================================================================
17 | #
18 | # count=0
19 | # while [ $count -le 10 ]
20 | # do
21 | # op item create --category=login --title="my example item $count" --vault= \
22 | # --url=https://www.1password.com --generate-password=20,letters,digits \
23 | # username=scott@acme.com \
24 | # 'Test Field 1[text]=my test secret' \
25 | # 'Additional Password 1[password]=mypassword1' \
26 | # 'Example.Additional Password 2[text]=mypassword2' \
27 | # 'Example.Additional Password 3[text]=mypassword3' \
28 | # 'Example.Additional Password 4[text]=mypassword4' \
29 | # 'Example.Additional Password 5[text]=mypassword5'
30 | # ((count++))
31 | # done
32 | #
33 | # ==============================================================================
34 | # CHANGE FIELD TYPE IN SAMPLE DATA
35 | # ==============================================================================
36 | # This script will change all custom text fields to password fields in the sample data created above.
37 | # The value stored in the field will remain unchanged. This example will change all items in a vault
38 | # however you could alternatively select items by tag, name, or UUID instead.
39 |
40 | vaultUUID=
41 | for item in $(op item list --vault $vaultUUID --format=json | jq --raw-output '.[].id')
42 | do
43 | op item edit $item 'Test Field 1[password]' \
44 | 'Example.Additional Password 2[password]' \
45 | 'Example.Additional Password 3[password]' \
46 | 'Example.Additional Password 4[password]' \
47 | 'Example.Additional Password 5[password]'
48 | done
--------------------------------------------------------------------------------
/item-management/recording-item-share.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | #This script help you automate the creation of a 1Password Item to use for an item-sharing link for the emails provided in the prompts
4 | #The script cleans up the 1Password item after generating the share-item link.
5 |
6 | #set the Role for the Item Title, e.g. 1Password Solutions Architect
7 | role="1Password Solutions Architect"
8 |
9 | op signin
10 |
11 | echo "Enter the customer organization name:"
12 | read company
13 |
14 | #creates Item Title based off company name
15 | itemTitle="${company} + ${role} - Zoom Recording"
16 |
17 | #takes multi-line input of typical zoom link copy
18 | echo -e "\033[0;31mEnter the copied share link from Zoom\033[0m"
19 | echo -e "\033[0;32mUse the Enter key twice to exit the input\033[0m"
20 | zoomPaste=$(sed '/^$/q')
21 | stringarray=($zoomPaste)
22 | zoomURL=${stringarray[0]}
23 | zoomPC=${stringarray[2]}
24 |
25 | #creates new item for sharing
26 | op item create --vault private --category=login --title="${itemTitle}" --url="${zoomURL}" password="${zoomPC}" notes="This recording will only be accessible for 7 days."
27 |
28 | #Create item share link to send out to the customer
29 | echo -e "\033[0;31mEnter the customer email address(es), comma seperated:\033[0m"
30 | read emailadd
31 | echo ""
32 | echo "Generating a Share Item link for" "$emailadd" "to access:" "$itemTitle"
33 | op item share "$itemTitle" --vault Private --emails $emailadd --expiry 168h
34 |
35 | echo -e "\033[0;32mCopy the link above and share this link with ${emailsadd}\033[0m"
36 |
37 | #deletes created item to clean up vault. (remove the archive flag if you want to fully delete the item)
38 | op item delete "$itemTitle" --vault Private --archive
39 |
--------------------------------------------------------------------------------
/item-management/requirements.txt:
--------------------------------------------------------------------------------
1 | tqdm
--------------------------------------------------------------------------------
/migration/README.md:
--------------------------------------------------------------------------------
1 | # Migration tools
2 |
3 | This section contains scripts to assist with migrating or importing data into 1Password.
4 |
5 | If you are migrating from another password management system, please visit to learn more about your options.
6 |
7 | If you are migrating from LastPass, read more about our LastPass importer here:
8 |
9 | ## Contents
10 |
11 | ### [/cleanup-scripts](./cleanup-scripts/)
12 | This directory contains several scripts to help people identify or clean up certain things after performing a migration from another password manager. Currently the scripts target certain tasks that LastPass migrators may encounter.
13 |
14 | * `vault_dedupe_helper.py` generates a csv-like report containing a list of all shared vaults you have access to, along with metadata about each vault. If, after migrating, you ended up with some duplicate vaults, this report might help you identify which duplicates to delete, and which one to retain
15 | * `add_vault_prefix.py` acts on a list of vault UUIDs. By default, it will prefix each vault name with `!` to make it easier to sort and further assess potential duplicates. This script can optionally be run in Delete Mode, which will delete the vault.
16 |
17 | ### [/lastpass](./lastpass/)
18 | This directory contains a script that offers a variety of ways to import data from LastPass to 1Password.
19 |
20 | We strongly encourage you to use the LastPass import tool included with 1Password for macOS, Windows, and Linux. If you are migrating from LastPass, read more about our LastPass importer here:
21 |
22 | However, this script may be helpful in certain scenarios, or as a demonstration of the capabilities of the [1Password CLI](https://developer.1password.com/docs/cli).
--------------------------------------------------------------------------------
/migration/cleanup-scripts/add_vault_prefix.py:
--------------------------------------------------------------------------------
1 | import subprocess
2 | import argparse
3 | import json
4 | import sys
5 |
6 | parser = argparse.ArgumentParser("Vault Prefixer", "Provide a path to a list of vault UUIDs.", "Prefixes all vaults passed to the script from the specified file with '!'.")
7 | parser.add_argument("--file", action="store", dest="filepath", help="Required. Specify the path to a file containing a linebreak-delimited list of vault UUIDs you'd like to process.", required=True)
8 | parser.add_argument("--delete", action="store_true", dest="deletemode", help="Use this option to delete vaults in the provided list, rather than append `!` to the vault name. USE WITH CAUTION.")
9 | args = parser.parse_args()
10 |
11 | # Prefix each vault in the input list with an exclamation mark to make them easy to locate and assess duplicate status. {}
12 | def prefixVault():
13 | print("Prefixing all listed vaults with '!' for easy sorting and identification.\n\n")
14 | with open(args.filepath, "r", encoding="utf-8") as vaultList:
15 | for vault in vaultList:
16 | vaultID = vault.rstrip()
17 | try:
18 | vaultDetails = json.loads(subprocess.run(f"op vault get {vaultID} --format=json", shell=True, check=True, capture_output=True).stdout)
19 | vaultName = vaultDetails['name']
20 | except (Exception) as err:
21 | print(f"Encountered an error getting the details for vault with ID {vaultID}: ", err)
22 | continue
23 | try:
24 | subprocess.run(["op", "vault", "edit", vaultID, f"--name=!{vaultName}"], check=True, capture_output=True)
25 | print(f"\tChanged \"{vaultName}\" => \"!{vaultName}\"")
26 | except (Exception) as err:
27 | print(f"Encountered an error renaming \"{vaultName}\": ", err)
28 | continue
29 |
30 | # Deletes each vault passed into this script
31 | def deleteVault():
32 | print("Deleting vaults provided to the script.\n\n")
33 | # TRIPLE CHECK that the script runner understands the implications of their decision
34 | understands = False
35 | if understands == False:
36 | print("\n\tDELETING VAULTS IS PERMANENT.\n\tBe very sure that you want to delete the vaults you are passing to this script.\n\n")
37 | response = input("I know what I am doing and UNDERSTAND THAT I CANNOT RESTORE DELETED VAULTS (yes/no): ")
38 | if response == "yes":
39 | understands = True
40 | else:
41 | sys.exit("If you'd like to proceed with deleting vaults, please run the script again and type 'yes' when prompted.")
42 |
43 | with open(args.filepath, "r", encoding="utf-8") as vaultList:
44 | for vault in vaultList:
45 | vaultID = vault.rstrip()
46 | try:
47 | subprocess.run(f"op vault delete {vaultID}", shell=True, check=True, capture_output=True)
48 | print(f"\tDeleted vault {vaultID}")
49 | except (Exception) as err:
50 | print(f"Encountered an error deleting vault with ID {vaultID}: ", err)
51 | continue
52 |
53 |
54 | if args.deletemode is True:
55 | deleteVault()
56 | else:
57 | prefixVault()
--------------------------------------------------------------------------------
/migration/cleanup-scripts/recreate_metadata_entry.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | import os
4 | import subprocess
5 | import csv
6 | import json
7 | from collections import defaultdict
8 | from datetime import datetime
9 |
10 | scriptPath = os.path.dirname(__file__)
11 | outputPath = scriptPath # Optionally choose an alternative output path here.
12 |
13 | class Vault:
14 | def __init__(self, csvRow):
15 | self.name = csvRow[0]
16 | self.uuid = csvRow[1]
17 | self.itemCount = csvRow[2]
18 | self.created = csvRow[3]
19 | self.updated = csvRow[4]
20 | self.userCount = csvRow[5]
21 |
22 |
23 | def findMetadataVaultUuid(vaults: dict):
24 | for k, v in vaults.items():
25 | if v == "❗️ LastPass Imported Shared Folders Metadata":
26 | return k
27 | return None
28 |
29 |
30 | def getVaultItems(vaultID):
31 | try:
32 | return subprocess.run(f"op item list --vault {vaultID} --format=json", shell=True, check=True, capture_output=True).stdout
33 | except (Exception) as err:
34 | print(f"Encountered an error getting items for vault {vaultID}: ", err)
35 | return
36 |
37 |
38 | def getItemDetails(vaultUuid, itemUuid):
39 | try:
40 | return subprocess.run(f"op item get {itemUuid} --vault {vaultUuid} --format=json", shell=True, check=True, capture_output=True).stdout
41 | except (Exception) as err:
42 | print(f"Encountered an error getting item details for item {itemUuid} vault {vaultUuid}: ", err)
43 | return
44 |
45 |
46 | def createItem(vaultUuid, itemJson: str):
47 | from subprocess import Popen, PIPE, STDOUT
48 | try:
49 | p = Popen(["op", "item", "create", "--vault", vaultUuid], stdout=PIPE, stdin=PIPE, stderr=PIPE)
50 | return p.communicate(input=itemJson.encode("utf-8"))[0]
51 | except (Exception) as err:
52 | print(f"Encountered an error creating an item in vault {vaultUuid}: ", err)
53 | return
54 |
55 |
56 | def readDate(date):
57 | return datetime.strptime(date, '%Y-%m-%d %H:%M:%S').timestamp()
58 |
59 |
60 | def findOriginalVault(vaults):
61 | minimum = (0, readDate(vaults[0].created))
62 | for i, vault in enumerate(vaults):
63 | date = readDate(vault.created)
64 | if date < minimum[1]:
65 | minimum = (i, date)
66 | return vaults[minimum[0]]
67 |
68 |
69 | def removeImportedPrefix(text):
70 | prefix = "Imported "
71 | if text.startswith(prefix):
72 | return text[len(prefix):]
73 | return text
74 |
75 |
76 | def main():
77 | # all vaults ordered by UUID
78 | vaults = {}
79 | # vaults ordered by name
80 | duplicateVaults = defaultdict(list)
81 | with open(f"{outputPath}/vaultreport.csv", "r", newline="") as vaultFile:
82 | csvReader = csv.reader(vaultFile)
83 | for row in csvReader:
84 | # we remove the 'Imported ' prefix from all vaults before processing them in case any
85 | # of them got renamed
86 | row[0] = removeImportedPrefix(row[0])
87 | duplicateVaults[row[0]].append(Vault(row))
88 | vaults[row[1]] = row[0]
89 |
90 | # find the metadata vault
91 | metadataVaultUuid = findMetadataVaultUuid(vaults)
92 | # get its items
93 | metadataItems = json.loads(getVaultItems(metadataVaultUuid))
94 | itemDetails = [json.loads(getItemDetails(metadataVaultUuid, item["id"])) for item in metadataItems]
95 | updatedFields = []
96 | for item in itemDetails:
97 | # find the LastPass metadata fields and process them
98 | for field in item.get("fields", []):
99 | ids = field["id"].split(".LPID.")
100 | if len(ids) != 2:
101 | continue
102 | vaultUuid = ids[0]
103 | lpId = ids[1]
104 | name = vaults.get(vaultUuid, "")
105 | # We found a metadata entry that points to a vault that exists.
106 | # We will find the vault with the same name, but that got created first
107 | # and we will save the new metadata entry
108 | if len(name) > 0:
109 | dupVaults = duplicateVaults[name]
110 | origVault = findOriginalVault(dupVaults)
111 | updatedFields.append({
112 | "id": f"{origVault.uuid}.LPID.{lpId}",
113 | "type": field["type"],
114 | "value": field["value"],
115 | })
116 |
117 | # create a new metadata record with the updated entries
118 | newItemFields = [{
119 | "id": "notesPlain",
120 | "type": "STRING",
121 | "purpose": "NOTES",
122 | "label": "notesPlain",
123 | "value": ""
124 | }]
125 | newItemFields.extend(updatedFields)
126 | newItem = {
127 | "title": "LastPass Metadata Recreated",
128 | "category": "SECURE_NOTE",
129 | "fields": newItemFields
130 | }
131 | createItem(metadataVaultUuid, json.dumps(newItem))
132 |
133 |
134 | if __name__ == "__main__":
135 | main()
136 |
--------------------------------------------------------------------------------
/migration/cleanup-scripts/remove_imported_prefix.py:
--------------------------------------------------------------------------------
1 | import subprocess
2 | import json
3 | import sys
4 |
5 |
6 | # Check CLI version
7 | def checkCLIVersion():
8 | r = subprocess.run(["op", "--version", "--format=json"], capture_output=True)
9 | major, minor = r.stdout.decode("utf-8").rstrip().split(".", 2)[:2]
10 | if not major == 2 and not int(minor) >= 25:
11 | sys.exit(
12 | "❌ You must be using version 2.25 or greater of the 1Password CLI. Please visit https://developer.1password.com/docs/cli/get-started to download the lastest version."
13 | )
14 |
15 |
16 | def getVaults():
17 | try:
18 | return subprocess.run(
19 | ["op", "vault", "list", "--permission=manage_vault", "--format=json"],
20 | check=True,
21 | capture_output=True,
22 | ).stdout
23 | except Exception as err:
24 | print(
25 | f"Encountered an error getting the list of vaults you have access to: ", err
26 | )
27 | return
28 |
29 |
30 | def main():
31 | checkCLIVersion()
32 | vaultList = json.loads(getVaults())
33 | print(
34 | "Removing 'Imported' prefix from all imported vaults in your 1Password account.\n\n"
35 | )
36 | for vault in vaultList:
37 | vaultID = vault["id"]
38 | vaultName = vault["name"]
39 | if vaultName.startswith("Imported "):
40 | trimmedName = vaultName.removeprefix("Imported ")
41 | try:
42 | subprocess.run(
43 | ["op", "vault", "edit", vaultID, f"--name={trimmedName}"],
44 | check=True,
45 | capture_output=True,
46 | )
47 | print(f'\t Changed "{vaultName}" => "{trimmedName}"')
48 | except Exception as err:
49 | print(f"Encountered an error renaming {vaultName}: ", err)
50 | continue
51 |
52 |
53 | main()
54 |
--------------------------------------------------------------------------------
/migration/cleanup-scripts/vault_dedupe_helper.py:
--------------------------------------------------------------------------------
1 | ###
2 | # A script that will produce a csv-like report to assist with de-duplication.
3 | # Outputs vaultName, vaultUUID, itemCount, vault creation date, vault updated, number of users
4 | ###
5 | import os
6 | import subprocess
7 | import csv
8 | import json
9 | from datetime import datetime
10 | import sys
11 |
12 | scriptPath = os.path.dirname(__file__)
13 | outputPath = scriptPath # Optionally choose an alternative output path here.
14 |
15 |
16 | # Check CLI version
17 | def checkCLIVersion():
18 | r = subprocess.run(["op", "--version", "--format=json"], capture_output=True)
19 | major, minor = r.stdout.decode("utf-8").rstrip().split(".", 2)[:2]
20 | if not major == 2 and not int(minor) >= 25:
21 | sys.exit(
22 | "❌ You must be using version 2.25 or greater of the 1Password CLI. Please visit https://developer.1password.com/docs/cli/get-started to download the lastest version."
23 | )
24 |
25 |
26 | # Get a list of vaults the logged-in user has access to
27 | def getVaults():
28 | try:
29 | return subprocess.run(
30 | ["op", "vault", "list", "--permission=manage_vault", "--format=json"],
31 | check=True,
32 | capture_output=True,
33 | ).stdout
34 | except Exception as err:
35 | print(
36 | f"Encountered an error getting the list of vaults you have access to: ", err
37 | )
38 | return
39 |
40 |
41 | # Get a list of users and their permissions for a vault
42 | def getVaultUserList(vaultID):
43 | try:
44 | return subprocess.run(
45 | f"op vault user list {vaultID} --format=json",
46 | shell=True,
47 | check=True,
48 | capture_output=True,
49 | ).stdout
50 | except Exception as err:
51 | print(
52 | f"Encountered an error getting the list of users for vault {vaultID}: ", err
53 | )
54 | return
55 |
56 |
57 | # Get the details of a vault
58 | def getVaultDetails(vaultID):
59 | try:
60 | return subprocess.run(
61 | f"op vault get {vaultID} --format=json",
62 | shell=True,
63 | check=True,
64 | capture_output=True,
65 | ).stdout
66 | except Exception as err:
67 | print(f"Encountered an error getting details for vault {vaultID}: ", err)
68 | return
69 |
70 |
71 | # Make dates returned from the CLI a bit nicer
72 | def formatDate(date):
73 | isoDate = datetime.fromisoformat(date)
74 | return f"{isoDate.year}-{isoDate.month}-{isoDate.day} {isoDate.hour}:{isoDate.minute}:{isoDate.second}"
75 |
76 |
77 | def main():
78 | checkCLIVersion()
79 | vaultList = json.loads(getVaults())
80 | with open(f"{outputPath}/vaultreport.csv", "w", newline="") as outputFile:
81 | csvWriter = csv.writer(outputFile)
82 | fields = [
83 | "vaultName",
84 | "vaultUUD",
85 | "itemCount",
86 | "vaultCreated",
87 | "vaultUpdated",
88 | "userCount",
89 | ]
90 | csvWriter.writerow(fields)
91 |
92 | for vault in vaultList:
93 | if vault["name"] == "Private":
94 | continue
95 | vaultUserList = json.loads(getVaultUserList(vault["id"]))
96 | vaultDetails = json.loads(getVaultDetails(vault["id"]))
97 | userCount = len(vaultUserList)
98 | dateCreated = formatDate(vaultDetails["created_at"])
99 | dateUpdated = formatDate(vaultDetails["updated_at"])
100 |
101 | csvWriter.writerow(
102 | [
103 | vault["name"],
104 | vault["id"],
105 | vaultDetails["items"],
106 | dateCreated,
107 | dateUpdated,
108 | userCount,
109 | ]
110 | )
111 |
112 | print(
113 | vault["name"],
114 | vault["id"],
115 | vaultDetails["items"],
116 | dateCreated,
117 | dateUpdated,
118 | userCount,
119 | )
120 |
121 |
122 | main()
123 |
--------------------------------------------------------------------------------
/migration/lastpass/folder_migrate.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | # This script will create 1Password vaults from each unique value
4 | # in the `grouping` column of a LastPass export generated through their
5 | # web-based exported or the lpass CLI (this has not
6 | # been tested on exports from their browser extension or other methods).
7 | # Shared/Nested folders in 1Password will have separate, non-nested
8 | # vaults created.
9 | #
10 | # This script does NOT create items in 1Password.
11 | import csv
12 | import json
13 | import subprocess
14 |
15 | from utils import normalize_vault_name
16 |
17 |
18 | def migrate_folders(csv_data, options):
19 | created_vault_list = {}
20 | lp_folder_list = set()
21 | is_csv_from_web_exporter = False
22 | stats = {
23 | 'total': 0,
24 | 'migrated': 0,
25 | 'skipped': 0,
26 | }
27 |
28 | linereader = csv.reader(csv_data, delimiter=',', quotechar='"')
29 | heading = next(linereader)
30 |
31 | if 'totp' in heading:
32 | is_csv_from_web_exporter = True
33 |
34 | for row in linereader:
35 | vault_name = row[6] if is_csv_from_web_exporter else row[5]
36 | if vault_name.startswith("Shared") and options['ignore-shared']:
37 | print(f"\tLastPass folder \"{vault_name}\" => skipped (ignore shared folders)")
38 | stats["skipped"] += 1
39 | continue
40 |
41 | if vault_name:
42 | lp_folder_list.add(normalize_vault_name(vault_name))
43 |
44 | stats['total'] = len(lp_folder_list)
45 | for folder in lp_folder_list:
46 | if not options['dry-run']:
47 | try:
48 | vault_create_command_output = subprocess.run([
49 | "op", "vault", "create",
50 | folder,
51 | "--format=json"
52 | ], check=True, capture_output=True)
53 | except:
54 | print(f"\tLastPass folder \"{folder}\" => skipped (cannot be created)")
55 | stats["skipped"] += 1
56 |
57 | continue
58 | new_vault_uuid = json.loads(vault_create_command_output.stdout)["id"]
59 | created_vault_list[folder] = new_vault_uuid
60 | print(f"\t\"{folder}\" => created")
61 | stats["migrated"] += 1
62 | else: # if dry run
63 | created_vault_list[folder] = folder
64 | print(f"\t\"{folder}\" => created; skipped (dry run)")
65 | stats["migrated"] += 1
66 |
67 | if not options['dry-run']:
68 | print(f"\nFolders migration complete!\nTotal {stats['total']} folders.\nCreated {stats['migrated']} vaults.\nSkipped {stats['skipped']}.")
69 | else:
70 | print(f"\nFolders migration dry run complete!\nTotal {stats['total']} folders.\nCreated {stats['migrated']} vaults.\nSkipped {stats['skipped']} folders.")
--------------------------------------------------------------------------------
/migration/lastpass/lastpass-migrate.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1Password/solutions/51d749b42523f5c4d8213696302d0183369a9ea7/migration/lastpass/lastpass-migrate.zip
--------------------------------------------------------------------------------
/migration/lastpass/lpass.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | import subprocess
3 | import sys
4 |
5 |
6 | def get_lp_data():
7 | # Sign user into LastPass
8 | lp_username = input("You may be prompted once for your LastPass username and twice for your Master Password.\nThe first prompt signs you into the LastPass CLI, the second prompt approves the export.\nWe don't store these credentials and will only be used for this session.\n\nPlease enter your LastPass username.\n>")
9 | try:
10 | subprocess.run(["lpass", "login", lp_username])
11 | except:
12 | sys.exit("Unable to sign into LastPass")
13 |
14 | # Issue export command to lpass
15 | try:
16 | lp_export = subprocess.run(["lpass", "export"], text=True, capture_output=True).stdout
17 | except:
18 | sys.exit("Unable to retrieve LastPass data")
19 |
20 | if len(lp_export) == 0:
21 | sys.exit("No items were exported from LastPass")
22 |
23 | return lp_export
24 |
--------------------------------------------------------------------------------
/migration/lastpass/main.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | import getopt
3 | import sys
4 | import io
5 | import lpass
6 | from folder_migrate import migrate_folders
7 | from vault_item_import import migrate_items
8 |
9 |
10 | def main(argv):
11 | options = {
12 | 'ignore-shared': False,
13 | 'dry-run': False,
14 | }
15 | csvfile = None
16 | is_migrating_folders = False
17 | is_migrating_items = False
18 | opts, args = getopt.getopt(argv, "fi", ["file=", "folders", "items", "ignore-shared", "dry-run"])
19 | for opt, arg in opts:
20 | if opt == "--file":
21 | print(f'Export secrets from csv file {arg}')
22 | csvfile = open(arg, newline='', encoding="utf8")
23 | continue
24 |
25 | if opt in ("-f", "--folders"):
26 | is_migrating_folders = True
27 | continue
28 |
29 | if opt in ("-i", "--items"):
30 | is_migrating_items = True
31 | continue
32 |
33 | if opt == "--ignore-shared":
34 | options["ignore-shared"] = True
35 | continue
36 |
37 | if opt == "--dry-run":
38 | options["dry-run"] = True
39 | continue
40 |
41 | if not is_migrating_items and not is_migrating_folders:
42 | sys.exit("Please specify the flag to run migration -i for items and folders, -f for folders only")
43 |
44 | if is_migrating_items and is_migrating_folders:
45 | sys.exit("Please specify single flag to run migration -i for items and folders, -f for folders only")
46 |
47 | if not csvfile:
48 | print('Export secrets using lpass cli.\n')
49 | csvfile = io.StringIO(lpass.get_lp_data())
50 |
51 | if is_migrating_folders:
52 | print('Migrating folders:')
53 | migrate_folders(csvfile, options)
54 | elif is_migrating_items:
55 | print('Migrating items:')
56 | migrate_items(csvfile, options)
57 |
58 | if csvfile:
59 | csvfile.close()
60 |
61 |
62 | if __name__ == "__main__":
63 | main(sys.argv[1:])
64 |
--------------------------------------------------------------------------------
/migration/lastpass/template_generator_test.py:
--------------------------------------------------------------------------------
1 | from template_generator import LPassData, TemplateGenerator
2 |
3 |
4 | def test_template_generator_credit_card():
5 | lpass_data = LPassData(
6 | url="http://sn",
7 | username="",
8 | password="",
9 | otp_secret="",
10 | notes="NoteType:Credit Card\nLanguage:en-GB\nName on Card:Test User\nType:card type\nNumber:4141414141414141\nSecurity Code:123\nStart Date:December,2020\nExpiration Date:October,2025\nNotes:Fake credit card",
11 | title="Fake card",
12 | vault="test",
13 | )
14 | template = TemplateGenerator(lpass_data).generate()
15 | assert template['title'] == 'Fake card'
16 | assert template['category'] == 'CREDIT_CARD'
17 |
18 | for field in template['fields']:
19 | if field['id'] == 'validFrom':
20 | assert field['value'] == '202012'
21 | elif field['id'] == 'expiry':
22 | assert field['value'] == '202510'
23 | elif field['id'] == 'cvv':
24 | assert field['value'] == '123'
25 | elif field['id'] == 'ccnum':
26 | assert field['value'] == '4141414141414141'
27 | elif field['id'] == 'type':
28 | assert field['value'] == 'card type'
29 | elif field['id'] == 'cardholder':
30 | assert field['value'] == 'Test User'
31 | elif field['id'] == 'notesPlain':
32 | assert field['value'] == 'Fake credit card'
33 |
34 |
35 | def test_template_generator_bank_account():
36 | lpass_data = LPassData(
37 | url="http://sn",
38 | username="",
39 | password="",
40 | otp_secret="",
41 | notes="NoteType:Bank Account\nLanguage:en-GB\nBank Name:bank name\nAccount Type:account type\nRouting Number:routing number\nAccount Number:account number\nSWIFT Code:swift code\nIBAN Number:iban number\nPin:pin\nBranch Address:branch address\nBranch Phone:branch phone\nNotes:note",
42 | title="Fake bank account",
43 | vault="test",
44 | )
45 | template = TemplateGenerator(lpass_data).generate()
46 | assert template['title'] == 'Fake bank account'
47 | assert template['category'] == 'BANK_ACCOUNT'
48 |
49 | for field in template['fields']:
50 | if field['id'] == 'notesPlain':
51 | assert field['value'] == 'note'
52 | elif field['id'] == 'bankName':
53 | assert field['value'] == 'bank name'
54 | elif field['id'] == 'accountType':
55 | assert field['value'] == 'account type'
56 | elif field['id'] == 'routingNo':
57 | assert field['value'] == 'routing number'
58 | elif field['id'] == 'accountNo':
59 | assert field['value'] == 'account number'
60 | elif field['id'] == 'swift':
61 | assert field['value'] == 'swift code'
62 | elif field['id'] == 'iban':
63 | assert field['value'] == 'iban number'
64 | elif field['id'] == 'telephonePin':
65 | assert field['value'] == 'pin'
66 | elif field['id'] == 'branchPhone':
67 | assert field['value'] == 'branch phone'
68 | elif field['id'] == 'branchAddress':
69 | assert field['value'] == 'branch address'
70 |
71 |
72 | def test_template_generator_secure_note():
73 | lpass_data = LPassData(
74 | url="http://sn",
75 | username="",
76 | password="",
77 | otp_secret="",
78 | notes="Some very important text",
79 | title="Secret message",
80 | vault="test",
81 | )
82 | template = TemplateGenerator(lpass_data).generate()
83 | assert template['title'] == 'Secret message'
84 | assert template['category'] == 'SECURE_NOTE'
85 | assert template['fields'][0]['value'] == 'Some very important text'
86 |
--------------------------------------------------------------------------------
/migration/lastpass/utils.py:
--------------------------------------------------------------------------------
1 | import re
2 | import calendar
3 |
4 |
5 | def normalize_vault_name(vault_name: str) -> str:
6 | return re.sub(r'[^a-zA-Z0-9\s-]', "_", vault_name)
7 |
8 |
9 | def lpass_date_to_1password_format(lpass_date: str) -> str:
10 | if not lpass_date:
11 | return ""
12 | [month, year] = lpass_date.split(",")
13 | monthNumber = list(calendar.month_abbr).index(month[:3])
14 | if monthNumber < 10:
15 | return year + "0" + str(monthNumber)
16 |
17 | return year + str(monthNumber)
18 |
--------------------------------------------------------------------------------
/migration/lastpass/utils_test.py:
--------------------------------------------------------------------------------
1 | # A debug script people simply migrating from LastPass to 1Password
2 | # can disregard as it is not used in the process migrating.
3 | import utils
4 |
5 |
6 | def test_normalize_vault_name():
7 | assert utils.normalize_vault_name("dev\sub_dev\\nested folder") == "dev_sub_dev_nested folder"
8 | assert utils.normalize_vault_name("dev/sub_dev") == "dev_sub_dev"
9 | assert utils.normalize_vault_name("test-folder") == "test-folder"
10 |
11 |
12 | def test_lpass_date_to_1password():
13 | date = utils.lpass_date_to_1password_format("October,2025")
14 | assert date == "202510"
15 |
16 | date2 = utils.lpass_date_to_1password_format("December,2023")
17 | assert date2 == "202312"
18 |
19 | date3 = utils.lpass_date_to_1password_format("January,2020")
20 | assert date3 == "202001"
21 |
--------------------------------------------------------------------------------
/migration/lastpass/vault_item_import.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | # This script will create vaults and login items from a LastPass export
4 | # generated through their web-based exported or the lpass CLI (this has not
5 | # been tested on exports from their browser extension or other methods).
6 | # Shared/Nested folders in 1Password will have separate, non-nested
7 | # vaults created. Items not belonging to any shared folder will be created
8 | # in the user's Private vault.
9 | #
10 | # Credit to @jbsoliman for the original script and @svens-uk for many significant enhancements
11 |
12 | import csv
13 | import json
14 | import subprocess
15 | import sys
16 |
17 | from utils import normalize_vault_name
18 | from template_generator import LPassData, TemplateGenerator
19 |
20 |
21 | def fetch_personal_vault():
22 | # Get the Private or Personal vault
23 | # The one listed first should be the correct one
24 | try:
25 | vault_list_command_output = subprocess.run([
26 | "op", "vault", "list",
27 | "--format=json"
28 | ], check=True, capture_output=True)
29 | except:
30 | sys.exit("An error occurred when attempting to fetch your 1Password vaults.")
31 |
32 | vault_list = json.loads(vault_list_command_output.stdout)
33 | personal_vault = next(filter(lambda x: (x["name"] == "Private" or x["name"] == "Personal"), vault_list), None)
34 |
35 | if personal_vault is None:
36 | sys.exit("Couldn't find your Personal or Private vault.")
37 |
38 | return personal_vault
39 |
40 |
41 | def create_item(vault: str, template):
42 | # Create item
43 | subprocess.run([
44 | "op", "item", "create", "-", f"--vault={vault}"
45 | ], input=template, text=True, stdout=subprocess.DEVNULL)
46 |
47 |
48 | def migrate_items(csv_data, options):
49 | created_vault_list = {}
50 | is_csv_from_web_exporter = False
51 | personal_vault = fetch_personal_vault()
52 | stats = {
53 | 'total': 0,
54 | 'migrated': 0,
55 | 'skipped': 0,
56 | 'vaults': 0,
57 | }
58 |
59 | linereader = csv.reader(csv_data, delimiter=',', quotechar='"')
60 | heading = next(linereader)
61 | if 'totp' in heading:
62 | is_csv_from_web_exporter = True
63 |
64 | for row in linereader:
65 | if len(row) == 0:
66 | continue
67 |
68 | stats['total'] += 1
69 | if is_csv_from_web_exporter:
70 | url = row[0]
71 | username = row[1]
72 | password = row[2]
73 | otp_secret = row[3]
74 | notes = row[4]
75 | title = row[5]
76 | vault = row[6]
77 | else:
78 | url = row[0]
79 | username = row[1]
80 | password = row[2]
81 | notes = row[3]
82 | title = row[4]
83 | vault = row[5]
84 | otp_secret = None
85 |
86 | if vault.startswith("Shared") and options['ignore-shared']:
87 | print(f"\t\"{title}\" => skipped (ignore shared credentials)")
88 | stats['skipped'] += 1
89 | continue
90 |
91 | # Check if vault is defined
92 | vault_defined = vault and vault != ""
93 | # Create vault, if needed
94 | if vault_defined and vault not in created_vault_list:
95 | if not options['dry-run']:
96 | try:
97 | normalized_vault_name = normalize_vault_name(vault)
98 | vault_create_command_output = subprocess.run([
99 | "op", "vault", "create",
100 | normalized_vault_name,
101 | "--format=json"
102 | ], check=True, capture_output=True)
103 | except:
104 | print(f"\t\"{vault}\" => failed to create new vault \"{normalized_vault_name}\"")
105 | continue
106 | new_vault_uuid = json.loads(vault_create_command_output.stdout)["id"]
107 | created_vault_list[vault] = new_vault_uuid
108 | stats["vaults"] += 1
109 | print(f"\tFrom LastPass folder \"{vault}\" => created new vault \"{normalized_vault_name}\"")
110 | else:
111 | normalized_vault_name = normalize_vault_name(vault)
112 | created_vault_list[vault] = vault
113 | print(f"\tFrom LastPass folder \"{vault}\" => created new vault \"{normalized_vault_name}\"; skipped (dry run)")
114 |
115 | template = TemplateGenerator(LPassData(
116 | url=url,
117 | username=username,
118 | password=password,
119 | otp_secret=otp_secret,
120 | notes=notes,
121 | title=title,
122 | vault=vault,
123 | )).generate()
124 |
125 | if not template:
126 | print(f"\t\"{title}\" => skipped (incompatible item)")
127 | stats["skipped"] += 1
128 | continue
129 |
130 | json_template = json.dumps(template)
131 | if not options['dry-run']:
132 | vault_to_use = created_vault_list[vault] if vault_defined else personal_vault['id']
133 |
134 | if options['dry-run']:
135 | print(f"\t\"{title}\" => migrated; skipped (dry run)")
136 | continue
137 |
138 | create_item(vault_to_use, json_template)
139 | stats["migrated"] += 1
140 | print(f"\t\"{title}\" => migrated")
141 |
142 | print(f"\nMigration complete!\nTotal {stats['total']} credentials.\nMigrated {stats['migrated']} credentials.\nCreated {stats['vaults']} vaults.\nSkipped {stats['skipped']} credentials.")
143 |
--------------------------------------------------------------------------------
/onepassword_sdks/README.md:
--------------------------------------------------------------------------------
1 | # 1Password SDK Examples
2 |
3 | Securely automate your workflows with SDKs from 1Password.
4 |
5 | # Introduction
6 | This repository contains example scripts and apps that leverage 1Password SDKs to address a variety of use cases.
7 |
8 | These examples are not intended to be used for production purposes. They are intended to demonstrate the capabilities of the SDKs, highlight specific use cases, and provide basic guidance for how to accomplish certain tasks with the SDKs.
9 |
10 | # Get started
11 | * [Learn more about 1Password SDKs.](https://developer.1password.com/docs/sdks)
12 | * [Follow the getting started tutorial.](https://developer.1password.com/docs/sdks/setup-tutorial)
13 |
14 | # Security Note
15 | * Most of these demo applications and scripts require the use of Service Account tokens and other secrets. Store all secrets securely, like in 1Password.
16 | * These example will make changes to data in 1Password. Do not use these examples with production or other real world data.
17 |
18 | # Examples
19 |
20 | ## [Use 1Password as a backend for a web app](./demo-inventory-tracker-webapp/)
21 | By: Scott Lougheed
22 | #### Description
23 | This web application allows you to create and manage "IT Resources", such as computers.
24 | * Create new "Devices", including their name, model, serial number, and Admin user credentials.
25 | * Each device is represented by an item in 1Password.
26 | * Display a list of all current devices.
27 | * Edit or delete existing devices.
28 |
29 | #### Featuring
30 | * 1Password Javascript SDK
31 | * 1Password Service Accounts
32 | * 1Password Secret References
33 | * 1Password CLI (`op run`)
34 |
35 | #### Highlights
36 | * Collect sensitive information from anyone using a web form and store that information securely in 1Password.
37 | * Display information from 1Password items on a webpage.
38 |
39 | #### Potential real world use cases
40 | * If you're a client services organization, collect sensitive information from your clients using a web portal and store it in 1Password, even if your clients aren't 1Password users.
41 | * Credentials for client-owned services you manage.
42 | * Credit card information for clients.
43 | * Provide limited access to information stored in 1Password, such as to employees in an internal app.
44 |
45 | ## [Migrate data between 1Password tenants](./demo-vault-migration-webapp/)
46 | By: Ron Doucette
47 |
48 | #### Description
49 | This web application facilitates the movement of vaults and their contents between two separate 1Password accounts.
50 |
51 | #### Featuring
52 | * 1Password Javascript SDK
53 | * 1Password CLI
54 | * 1Password Service Accounts
55 |
56 | #### Highlights
57 | * Securely move data between two 1Password accounts without writing any data to disk.
58 | * Use 1Password SDKs to back a web front-end.
59 |
60 | #### Potential real world use cases
61 | * Consolidating 1Password information across multiple accounts during mergers, acquisitions, or other organizational transitions.
62 |
63 |
64 | ## [Share files and markdown securely with Secure Item Sharing Links](./demo-share-script/)
65 | By: Amanda Crawley
66 | #### Description
67 | This Python script creates a 1Password item from files in a directory of your choice for the purposes of securely sharing source code and a README.
68 |
69 | * A `README.md` file present in the directory becomes the markdown-formatted contents of the Notes section in a 1Password item.
70 | * Other files in the directory are attached to the 1Password item.
71 |
72 | Finally, the script produces a Secure Item Sharing Link so you can securely share the item with the intended recipient.
73 |
74 | #### Featuring
75 | * 1Password Python SDK
76 | * 1Password Service Accounts
77 | * File attachments to 1Password items
78 | * Item sharing links
79 |
80 | #### Highlights
81 | * Use the SDK to build bespoke command-line utilities.
82 | * Use the SDK to attach files to 1Password items.
83 |
84 | #### Potential real world use cases
85 | * Programmatically write or read cert or key files stored as attachments in 1Password for machine-to-machine authentication.
86 | * Share text and files securely with anyone, whether they use 1Password or not.
87 | * Create custom sharing utilities.
88 |
89 | ## [Secure and streamline employee onboarding with Okta](./demo-share-okta-user-script/)
90 | By: Amanda Crawley
91 | #### Description
92 | This Python script is an example of how you can streamline and secure employee onboarding. The script allows you to:
93 |
94 | * Create a new Okta user and generate a strong password for their Okta account.
95 | * Get a Secure Item Sharing Link for the new user's Okta credentials in 1Password. This link can be shared with the new employee, like by sending it to their personal email address.
96 |
97 | #### Featuring
98 | * 1Password Python SDK
99 | * Secure Item Sharing Links
100 | * Okta API
101 |
102 | #### Highlights
103 | * Use 1Password SDKs to interact with additional third party services.
104 | * Use 1Password SDKs to generate secure passwords for external services and store the information in 1Password.
105 | * Share 1Password items securely with anyone, whether they use 1Password or not.
106 |
107 |
108 | #### Potential real world use cases
109 | * Onboarding new employees, particularly when Okta is required to access email inboxes or where inboxes may not be provisioned quickly enough.
--------------------------------------------------------------------------------
/onepassword_sdks/demo-inventory-tracker-webapp/.env.template:
--------------------------------------------------------------------------------
1 | # Learn about 1Password Secret References https://developer.1password.com/docs/cli/secret-reference-syntax/
2 | OP_SERVICE_ACCOUNT_TOKEN = "Secret Reference to Service Account Token in 1Password"
3 | OP_VAULT_ID = "SOME VAULT UUID"
--------------------------------------------------------------------------------
/onepassword_sdks/demo-inventory-tracker-webapp/Dockerfile:
--------------------------------------------------------------------------------
1 | # syntax=docker/dockerfile:1
2 |
3 | ARG NODE_VERSION=22.0.0
4 |
5 | FROM node:${NODE_VERSION}-alpine
6 |
7 | ENV NODE_ENV=production
8 |
9 | WORKDIR /usr/src/app
10 |
11 | RUN --mount=type=bind,source=package.json,target=package.json \
12 | --mount=type=bind,source=package-lock.json,target=package-lock.json \
13 | --mount=type=cache,target=/root/.npm \
14 | npm ci --omit=dev
15 |
16 | USER node
17 |
18 | COPY . .
19 |
20 | EXPOSE 3000
21 |
22 | CMD ["npm", "run", "prod"]
--------------------------------------------------------------------------------
/onepassword_sdks/demo-inventory-tracker-webapp/README.md:
--------------------------------------------------------------------------------
1 | # JS SDK Example: Computer Inventory Tracker
2 |
3 | ## Prerequisites
4 | * [Docker Engine](https://docs.docker.com/get-started/get-docker/)
5 | * [1Password CLI](https://developer.1password.com/docs/cli/get-started)
6 | * A [1Password Service Account](https://developer.1password.com/docs/service-accounts) with read and write permissions in at least one vault.
7 | * Learn about [secret references](https://developer.1password.com/docs/cli/secret-reference-syntax/)
8 |
9 | ## Deploy the Web App
10 | 1. Create a [1Password Service Account](https://start.1password.com/developer-tools/infrastructure-secrets/serviceaccount/). Make sure to save the service account token in your 1Password account.
11 | 2. Add the [UUID](https://developer.1password.com/docs/sdks/concepts#unique-identifiers) of a vault the service account has read and write permissions in to the `.env` file. This is where 1Password items managed by the app will be stored.
12 | 3. Add the secret reference to the `.env.template` file and remove the `.template` suffix.
13 | 4. Add the UUID of a vault the Service Account has read and write permissions in to the .env file. This is where 1Password items managed by the app will be stored.
14 | 5. Build the image and deploy the container:
15 | ```
16 | op run --env-file=.env -- docker compose up --build
17 | ```
18 | 6. Visit the webapp in your browser on port 3000 (for example, `localhost:3000`)
--------------------------------------------------------------------------------
/onepassword_sdks/demo-inventory-tracker-webapp/app.js:
--------------------------------------------------------------------------------
1 | import sdk from "@1password/sdk";
2 | import express from "express";
3 | import bodyParser from "body-parser";
4 |
5 | const app = express();
6 | app.set("view engine", "ejs");
7 | app.use(bodyParser.urlencoded({ extended: true }));
8 | app.use(bodyParser.json());
9 | app.use(express.static("public"));
10 |
11 | const vaultId = process.env.OP_VAULT_ID;
12 | const port = 3000;
13 |
14 | const opClientConfig = {
15 | auth: process.env.OP_SERVICE_ACCOUNT_TOKEN,
16 | integrationName: "Inventory Tracker",
17 | integrationVersion: "v1.0.0",
18 | };
19 |
20 | const client = await sdk.createClient(opClientConfig);
21 |
22 | const createItem = async (vaultID, data) => {
23 | console.log("Creating item with");
24 |
25 | const item = await client.items.create({
26 | title: data.deviceName,
27 | category: sdk.ItemCategory.Login,
28 | vaultId: vaultID,
29 | fields: [
30 | {
31 | id: "username",
32 | title: "username",
33 | fieldType: sdk.ItemFieldType.Text,
34 | value: data.adminUsername,
35 | },
36 | {
37 | id: "password",
38 | title: "password",
39 | fieldType: sdk.ItemFieldType.Concealed,
40 | value: data.password,
41 | },
42 | {
43 | id: "deviceName",
44 | title: "Device Name",
45 | sectionId: "deviceDetails",
46 | fieldType: sdk.ItemFieldType.Text,
47 | value: data.deviceName,
48 | },
49 | {
50 | id: "deviceModel",
51 | title: "Model Number",
52 | sectionId: "deviceDetails",
53 | fieldType: sdk.ItemFieldType.Text,
54 | value: data.deviceModel,
55 | },
56 | {
57 | id: "deviceSerial",
58 | title: "Serial Number",
59 | sectionId: "deviceDetails",
60 | fieldType: sdk.ItemFieldType.Text,
61 | value: data.deviceSerial,
62 | },
63 | {
64 | id: "adminUsername",
65 | title: "Device Admin Username",
66 | sectionId: "deviceDetails",
67 | fieldType: sdk.ItemFieldType.Text,
68 | value: data.adminUsername,
69 | },
70 | ],
71 | sections: [
72 | {
73 | id: "deviceDetails",
74 | title: "Device Details",
75 | },
76 | ],
77 | });
78 | return item;
79 | };
80 |
81 | const generatePassword = async () => {
82 | return sdk.Secrets.generatePassword({
83 | type: "Random",
84 | parameters: {
85 | includeDigits: true,
86 | includeSymbols: true,
87 | length: 32,
88 | },
89 | });
90 | };
91 |
92 | const updateItem = async (updatedItemData, itemID) => {
93 | console.log(`Updating item with ID: ${itemID}`);
94 | const oldItem = await getOneItem(itemID);
95 |
96 | const newItem = {
97 | ...oldItem,
98 | title: updatedItemData.deviceName,
99 | fields: oldItem.fields.map((field) => {
100 | if (field.id === "password") {
101 | if (updatedItemData.password !== "") {
102 | return { ...field, value: updatedItemData.password };
103 | }
104 | return field;
105 | } else if (field.id === "adminUsername" || field.id === "username") {
106 | return {
107 | ...field,
108 | value: updatedItemData.adminUsername,
109 | };
110 | } else if (updatedItemData[field.id]) {
111 | return { ...field, value: updatedItemData[field.id] };
112 | }
113 | return field;
114 | }),
115 | };
116 | const updatedItem = await client.items.put(newItem);
117 | return updatedItem;
118 | };
119 |
120 | const getAllItems = async () => {
121 | const itemList = await client.items.listAll(vaultId);
122 |
123 | const itemPromises = itemList.elements.map(async (item) => {
124 | return await client.items.get(vaultId, item.id);
125 | });
126 |
127 | const items = await Promise.all(itemPromises);
128 | return items;
129 | };
130 |
131 | const getOneItem = async (itemId) => {
132 | const foundItem = await client.items.get(vaultId, itemId);
133 | return foundItem;
134 | };
135 |
136 | const deleteItem = async (itemId) => {
137 | console.log(`Attempting to delete item with ID: ${itemId}`);
138 | await client.items.delete(vaultId, itemId);
139 | };
140 |
141 | app.get("/", (req, res) => {
142 | res.render("index", {
143 | title: "Knox IT Resource Manager",
144 | currentPage: "home",
145 | item: null,
146 | });
147 | });
148 |
149 | app.get("/generate-password", async (req, res) => {
150 | try {
151 | const generatedPassword = await generatePassword();
152 | res.json({ generatedPassword: generatedPassword.password });
153 | } catch (error) {}
154 | });
155 |
156 | app.get("/items", async (req, res) => {
157 | try {
158 | const items = await getAllItems();
159 | res.render("items", {
160 | items,
161 | currentPage: "item",
162 | title: "Inventory List",
163 | });
164 | } catch (error) {
165 | console.error("Error fetching items:", error);
166 | res.status(500).send("Oops, something went wrong getting items");
167 | }
168 | });
169 |
170 | app.get("/items/:item", async (req, res) => {
171 | try {
172 | const item = await client.items.get(vaultId, req.params.item);
173 | res.render("item", { item, currentPage: "item", title: "Item Details" });
174 | } catch (error) {
175 | console.error("Error fetching item:", error);
176 | res.status(404).send(`Unable to find item with UUID: ${req.params.item}`);
177 | }
178 | });
179 |
180 | app.get("/edit/:item", async (req, res) => {
181 | try {
182 | const itemId = req.params.item;
183 | const item = await getOneItem(itemId);
184 |
185 | res.render("index", { item, currentPage: "item", title: "Edit Item" });
186 | } catch (error) {
187 | console.error("Error fetching item:", error);
188 | res.status(500).send("There was an error");
189 | }
190 | });
191 |
192 | app.post("/submit", async (req, res) => {
193 | const {
194 | itemId,
195 | deviceName,
196 | deviceModel,
197 | deviceSerial,
198 | adminUsername,
199 | password,
200 | } = req.body;
201 | const updatedItemData = {
202 | deviceName,
203 | deviceModel,
204 | deviceSerial,
205 | adminUsername,
206 | password,
207 | };
208 | try {
209 | if (itemId) {
210 | const updatedItem = await updateItem(updatedItemData, itemId);
211 | res.redirect(`items/${updatedItem.id}`);
212 | } else {
213 | const item = await createItem(vaultId, updatedItemData);
214 | res.redirect(`items/${item.id}`);
215 | }
216 | } catch (error) {
217 | console.error("Error submitting item:", error);
218 | res.status(500).send("There was an error");
219 | }
220 | });
221 |
222 | app.post("/delete", async (req, res) => {
223 | try {
224 | const { itemId } = req.body;
225 | await deleteItem(itemId);
226 | res.redirect("/items");
227 | } catch (error) {
228 | console.error("Error deleting item:", error);
229 | res.status(500).send("There was an error");
230 | }
231 | });
232 |
233 | app.listen(port, () => {
234 | console.log(`Listening on port ${port}`);
235 | });
236 |
--------------------------------------------------------------------------------
/onepassword_sdks/demo-inventory-tracker-webapp/docker-compose.yml:
--------------------------------------------------------------------------------
1 | services:
2 | it-tracker:
3 | build:
4 | context: .
5 | environment:
6 | NODE_ENV: production
7 | OP_SERVICE_ACCOUNT_TOKEN:
8 | OP_VAULT_ID:
9 | ports:
10 | - 3000:3000
11 |
--------------------------------------------------------------------------------
/onepassword_sdks/demo-inventory-tracker-webapp/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "it-tracker",
3 | "version": "1.0.0",
4 | "main": "app.js",
5 | "scripts": {
6 | "dev": "op run --env-file=.env -- nodemon app.js",
7 | "prod": "node app.js"
8 | },
9 | "keywords": [],
10 | "author": "1Password",
11 | "license": "ISC",
12 | "description": "Tool to track device inventory and admin credentials in 1Password.",
13 | "type": "module",
14 | "dependencies": {
15 | "@1password/sdk": "^0.2.1",
16 | "body-parser": "^1.20.2",
17 | "ejs": "^3.1.10",
18 | "express": "^4.19.2",
19 | "nodemon": "^3.1.4"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/onepassword_sdks/demo-inventory-tracker-webapp/public/styles.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --primary-color: #007bff;
3 | --primary-color-dark: #0056b3;
4 | --text-color: #333;
5 | --label-color: #555;
6 | --background-color: #f4f4f4;
7 | }
8 | body {
9 | font-family: Arial, sans-serif;
10 | margin: 20px;
11 | background-color: var(--background-color);
12 | }
13 |
14 | .container {
15 | background: white;
16 | border-radius: 8px;
17 | box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
18 | padding: 1em;
19 | max-width: 500px;
20 | margin: auto;
21 | }
22 |
23 | .container.item {
24 | margin-bottom: 0.5em;
25 | }
26 |
27 | h1 {
28 | text-align: center;
29 | color: var(--text-color);
30 | }
31 | h2 {
32 | text-align: center;
33 | }
34 |
35 | .field {
36 | margin-bottom: 15px;
37 | }
38 |
39 | .field-title {
40 | font-weight: bold;
41 | color: var(--label-color);
42 | }
43 |
44 | .field-value {
45 | margin-left: 10px;
46 | color: var(--text-color);
47 | display: block;
48 | }
49 |
50 | .section-title {
51 | font-size: 1em;
52 | font-weight: 800;
53 | margin-top: 20px;
54 | color: var(--primary-color);
55 | }
56 |
57 | label {
58 | font-weight: bold;
59 | color: var(--label-color);
60 | display: block;
61 | margin-bottom: 5px;
62 | }
63 |
64 | input[type="text"],
65 | input[type="password"] {
66 | width: 100%;
67 | padding: 10px;
68 | margin-bottom: 15px;
69 | border: 1px solid #ccc;
70 | border-radius: 4px;
71 | box-sizing: border-box;
72 | }
73 |
74 | input[type="submit"] {
75 | background-color: #007bff;
76 | color: white;
77 | border: none;
78 | border-radius: 4px;
79 | padding: 10px 15px;
80 | cursor: pointer;
81 | font-size: 16px;
82 | width: 100%;
83 | }
84 |
85 | input[type="submit"]:hover {
86 | background-color: var(--primary-color-dark); /* Darker shade on hover */
87 | }
88 |
89 | .nav-menu {
90 | display: flex;
91 | max-width: 25%;
92 | margin-inline: auto;
93 | justify-content: center;
94 | margin-bottom: 0.5em;
95 | color: white;
96 | font-size: medium;
97 | }
98 |
99 | .nav-menu > nav > ul {
100 | list-style: none;
101 | padding: 0;
102 | margin: 0;
103 | display: flex;
104 | }
105 |
106 | .nav-menu > nav > ul > li {
107 | background-color: var(--primary-color);
108 | padding: 0.4em;
109 | margin: 0 15px;
110 | border-radius: 0.4em;
111 | }
112 |
113 | .nav-menu > nav > ul > li.active,
114 | .nav-menu > nav > ul > li:hover {
115 | background-color: var(--primary-color-dark);
116 | border-radius: 0.25em;
117 | }
118 |
119 | .nav-menu > nav > ul > li > a {
120 | text-decoration: none;
121 | color: inherit;
122 | }
123 |
124 | .item-manage-button {
125 | margin-bottom: 0.3em;
126 | }
127 |
128 | .button-container {
129 | display: flex;
130 | justify-content: space-between;
131 | gap: 1em;
132 | }
133 |
134 | .admin-password-container {
135 | display: flex;
136 | justify-content: space-between;
137 | gap: 1em;
138 | align-items: baseline;
139 | max-height: 100%;
140 | }
141 |
142 | .admin-password-container input[type="password"] {
143 | flex: 1;
144 | }
145 |
146 | .admin-password-container button {
147 | display: flex;
148 | background-color: var(--primary-color);
149 | color: white;
150 | border: none;
151 | border-radius: 4px;
152 | height: 2.25rem;
153 | align-items: center;
154 | cursor: pointer;
155 | }
156 |
--------------------------------------------------------------------------------
/onepassword_sdks/demo-inventory-tracker-webapp/views/includes/header.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | <%= title %>
8 |
9 |
10 |
11 |
12 |
13 |