Compare commits

...

78 Commits

Author SHA1 Message Date
Aetherinox fe77e5399a
ci: update workflow 2024-05-03 11:16:50 -07:00
Aetherinox 90ab4075af
ci: temp workflow edits 2024-05-03 11:13:10 -07:00
Aetherinox daab8c9693
ci: update workflows 2024-05-03 11:05:58 -07:00
Aetherinox cbae9af18b
ci: cloudflare assign subdomain 2024-05-03 07:49:40 -07:00
Aetherinox 1be7a55eab
ci: add cloudflare workflow for app.keeweb.info 2024-05-03 07:01:28 -07:00
Kevin McCormack c1fda05c77
Merge pull request #1944 from R3dIO/master
Fixed issue with Csv parser parse (#1904)
2024-04-09 08:12:31 -04:00
Aetherinox fb6aaff519
fix: convert space character to non-breaking space on password reveal
converts spaces and a few other characters empty characters into non-breaking spaces so that they'll render properly in the password field.
2024-04-07 12:46:16 -07:00
Aetherinox ab9b5a9d68
fix: prettify linting
rage inducing
2024-04-07 12:44:46 -07:00
Aetherinox fe23aa9097
chore: conform to prettify 2024-04-07 12:11:43 -07:00
Aetherinox 0f8332b07a
fix: convert space character to non-breaking space on password reveal
addresses issue with having space characters in passwords and pressing the 'reveal password' button.
2024-04-07 12:06:11 -07:00
Aetherinox bd87c6f2de
repo: convert issue templates into forms
label names updated, switched issue templates to forms
2024-04-06 14:56:25 -07:00
Aetherinox 6690d01643
repo: remove original git issue templates 2024-04-06 14:48:38 -07:00
Aetherinox 331ec8c738
repo: convert issue templates into forms 2024-04-06 14:45:40 -07:00
Aetherinox 570b365f0a
fix: support multiple otpauth url structures
addresses issue with being unable to format otpauth url as otpauth://totp
2024-04-06 09:53:53 -07:00
Aetherinox c45d7b1eb5
Merge branch 'master' of github.com:Aetherinox/keeweb 2024-04-06 09:47:01 -07:00
Aetherinox 80370d128c
fix: support for different otpauth url syntax
adds support for otpauth://totp? and otpauth://totp/
2024-04-06 09:45:54 -07:00
Aetherinox 6e63a3db52
feat: support for different otpauth url syntax
adds support for otpauth://totp? and otpauth://totp/
2024-04-06 09:38:36 -07:00
Aetherinox d0d163bf5e
Merge pull request #2146 from Aetherinox/master
fix: addresses not being able to unset a keyfile once added to a vault
2024-04-05 13:44:41 -07:00
Aetherinox 738d9c6a00
fix: addresses not being able to unset a keyfile once added to a vault
Refs: #2073 #1924 #2038 #1941
2024-04-05 13:43:02 -07:00
Aetherinox fcdce278a7
Merge pull request #2052 from HarlemSquirrel/electron-13
Bump electron 12.0.7 to 13.6.9
2024-04-04 18:49:31 -07:00
Aetherinox e4fd64d1cf
Merge pull request #2144 from Aetherinox/master
fix: add legacy support for npm run dev
2024-04-04 18:18:38 -07:00
Aetherinox 0fd3690ec2
Merge branch 'master' of github.com:Aetherinox/keeweb 2024-04-04 17:18:43 -07:00
Aetherinox 08c4f844ec
change: add dev-legacy / set-legacy to package scripts
Due to some outdated packages, some users may attempt to run dev and experience the error "error:0308010C:digital envelope routines::unsupported"

If npm run dev does not work; use npm run dev-legacy
2024-04-04 17:16:51 -07:00
Kevin McCormack a96a4df3a4 Merge branch 'master' into electron-13 2024-04-04 17:10:15 -04:00
Kevin McCormack c5f8b28b33
Merge pull request #2142 from HarlemSquirrel/add-verify-workflow
Add verify workflow
2024-04-04 17:04:10 -04:00
Kevin McCormack 5ea7328bb8 Add workflow_dispatch to verify workflow 2024-04-04 17:01:30 -04:00
Kevin McCormack b09e1344cc fixup! Add lint job 2024-04-04 16:53:44 -04:00
Kevin McCormack 790b32e5d0 Add lint job 2024-04-04 16:53:44 -04:00
Kevin McCormack 2fb3357ed3 Update caniuse 2024-04-04 16:53:44 -04:00
Kevin McCormack 72b25a2c20 fixup! Add verify workflow 2024-04-04 16:53:44 -04:00
Kevin McCormack 9d346490f7 fixup! Add verify workflow 2024-04-04 16:53:44 -04:00
Kevin McCormack 8bb2719b21 Add verify workflow 2024-04-04 16:53:44 -04:00
Kevin McCormack 3dda7f171b Add .nvmrc 2024-04-04 16:53:44 -04:00
Aetherinox a33f1e0ba1
Merge pull request #2143 from Aetherinox/dev
fix: specify puppeteer argument to fix test builds for ci
2024-04-04 13:47:35 -07:00
Aetherinox ce7838d935
ci: remove test verify workflow 2024-04-04 13:36:44 -07:00
Aetherinox 7e06762b25
chore: cleanup tests 2024-04-04 13:19:16 -07:00
Aetherinox 1499fd789b
dep: update packages 2024-04-04 13:16:56 -07:00
Aetherinox ae4bce3a11
ci: update verify 2024-04-04 13:10:00 -07:00
Aetherinox 2aa6e05b53
fix: modify puppeteer arguments to fix test builds for ci keeweb/keeweb#2142
ref:
keeweb/keeweb#2142
2024-04-04 13:02:42 -07:00
Kevin McCormack 62b3458187 Bump electron to 13 2022-09-20 21:58:22 -04:00
Dimitri Witkowski e6df216291
Update README.md 2022-06-09 20:16:26 +02:00
Anuj Koli 3fff58353c Fixed issue with Csv parser parse (#1904) 2021-12-05 11:13:01 -05:00
antelle dfc6201285
Merge branch 'release-1.18' 2021-07-18 16:21:23 +02:00
antelle 1930c265c8
Merge branch 'master' into release-1.18 2021-07-18 16:21:06 +02:00
antelle 1c434d7c79
release notes 2021-07-18 16:20:52 +02:00
antelle c0c75ac8a8
Merge branch 'master' into release-1.18 2021-07-18 16:19:59 +02:00
antelle 4b7124aa57
disabled automatic installation of KeePassXC-Browser extension 2021-07-18 16:19:28 +02:00
antelle 93cee76b4f
up translations 2021-07-18 16:09:59 +02:00
antelle 1673580dfa
bump version 2021-07-18 16:06:15 +02:00
antelle bf0e94582d
release notes 2021-07-18 16:06:14 +02:00
antelle 29ae7e8ed2
fix #1845: fixed an error during writing to closed socket 2021-07-18 16:06:13 +02:00
antelle 9b2c40a078
added yubikeys code logging 2021-07-18 16:06:12 +02:00
antelle f0bb382449
fixed variable usage 2021-07-18 16:06:11 +02:00
antelle 062ddb3f72
added MS Teams apps 2021-07-18 16:06:10 +02:00
antelle 208a1b551e
renames some ms teams constants 2021-07-18 16:06:09 +02:00
Saulo Alves f8af64e45b
renamed teams to microsoft teams; fixed grunt prettier error in onedrive 2021-07-18 16:06:08 +02:00
Saulo Alves 98268f21c0
added teams storage; added tenant id configuration for onedrive and teams 2021-07-18 16:06:07 +02:00
antelle dcbe8d70da
fixed donate button border 2021-07-18 16:06:06 +02:00
Dimitri Witkowski 1d61c6ca03
Merge pull request #1838 from magnetikonline/fix-keeweb-connect-extension-link
Fix broken links to download the KeeWeb Connect browser extension
2021-06-19 20:12:38 +02:00
Peter Mescalchin 433f0acf3c fix broken links to download KeeWeb Connect browser extension due to incorrect const/Links property 2021-06-05 15:11:53 +10:00
antelle 070573e68a
Merge branch 'release-1.18' 2021-05-19 10:35:35 +02:00
antelle 946d7769f1
bump version 2021-05-19 10:33:41 +02:00
antelle f0426a3ec2
fix #1820: minimize on close on macOS 2021-05-19 10:33:17 +02:00
antelle b2d67818a8
fix #1818: extension connection error if browser cannot be identified 2021-05-19 10:16:40 +02:00
antelle c0b159d824
fix #1824: saving KDBX3 files with compression disabled 2021-05-19 10:07:59 +02:00
antelle d590318153
Merge branch 'release-1.18' 2021-05-14 21:02:12 +02:00
antelle 5816ca9c4d
bump version 2021-05-14 21:01:43 +02:00
antelle 6627e8c0ec
fix #1817: crash on files with large attachments as KDBX3 2021-05-14 21:00:43 +02:00
antelle 379e28e8a4
about version in the the bug report template 2021-05-14 20:56:48 +02:00
antelle 4fb25451a2
fix #1816: old Chromium support, such as Android Edge 2021-05-13 10:45:48 +02:00
antelle f59b846643
Merge branch 'release-1.18' 2021-05-12 21:40:25 +02:00
antelle 28d7dfeb67
bump version 2021-05-12 21:39:47 +02:00
antelle 6cc39d2107
fix #1808: restore KeeWeb from system tray on extension request 2021-05-12 21:38:54 +02:00
antelle a92a01095b
fix #1814: option to disable auto-type title filter by default 2021-05-12 21:00:51 +02:00
antelle 3f4cddd2a2
fix #1811: app doesn't quit during update 2021-05-12 20:50:24 +02:00
antelle aa734ea976
fix #1813: fixed custom icon selection 2021-05-11 18:50:37 +02:00
antelle 30628004fd
https link in the readme 2021-05-10 20:40:38 +02:00
antelle a6e87b52fd
fix #1810: fixed the extension on old macOS starting 10.11 2021-05-10 20:40:30 +02:00
44 changed files with 1932 additions and 574 deletions

View File

@ -7,6 +7,8 @@
}],
"@babel/plugin-proposal-optional-catch-binding",
"@babel/plugin-proposal-optional-chaining",
"@babel/plugin-proposal-nullish-coalescing-operator",
"@babel/plugin-proposal-logical-assignment-operators",
"@babel/plugin-external-helpers"
]
}

View File

@ -1,34 +0,0 @@
---
name: Bug report
about: Create a report to help us improve KeeWeb
title: ''
labels: 'bug'
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Environment**
Your user-agent (from Settings/Help section)
**Kdbx File**
Does it happen on Demo or New database?
If you have a test db without your personal data, please attach it.
**Logs**
Please open dev tools in your browser and attach output log from Console tab. If you are using a desktop app, devtools can be opened from Settings/General/Advanced.

169
.github/ISSUE_TEMPLATE/bug_report.yaml vendored Normal file
View File

@ -0,0 +1,169 @@
name: "🐛 Bug Report"
description: Create a report to help us improve KeeWeb!
title: "🐛 Bug: <title>"
labels: [
"Type ◦ Bug"
]
body:
- type: markdown
attributes:
value: |
1. Please speak `English`.
2. Make sure you are using the latest version and take a moment to check that your issue hasn't been reported before.
3. It's really important to provide pertinent details and logs,
incomplete details will be handled as an invalid report.
<br />
- type: dropdown
id: priority-type
attributes:
label: Priority
description: |
How critical is the issue?
Do not abuse this. Issues that completely break KeeWeb would be classified as critical.
If you are requesting a new feature, then it would be low or normal.
options:
- "Low"
- "Normal"
- "High"
- "Urgent"
validations:
required: true
- type: input
id: ver-keeweb
attributes:
label: "Version: KeeWeb"
description: |
Version of KeeWeb you are running
Version is located in Settings Help App Information
placeholder: "Ex: v1.18.7"
validations:
required: true
- type: dropdown
id: build-env
attributes:
label: Environment
description: |
What environment are you running KeeWeb as?
options:
- "Web"
- "Desktop"
- type: dropdown
id: build-os
attributes:
label: Operating System
description: |
Select which OS you are running:
options:
- "Windows"
- "Mac"
- "Linux"
- type: dropdown
id: issue-target
attributes:
label: Range of issue
description: |
Where does this issue occur?
Before submitting your bug report, try to see if the issue also occurs on the official demo site at https://app.keeweb.info/
options:
- "The issue occurs on my own setup AND demo site"
- "The issue only occurs on my own setup, demo site works correctly"
- type: dropdown
id: issue-db-type
attributes:
label: Database Type
description: |
What type of database are you having this issue with?
options:
- "Issue occurs only on new databases"
- "Issue occurs only on existing databases"
- "Issue occurs on both new and existing databases"
- type: textarea
id: logs-env
attributes:
label: Environment Copy/Paste
description: |
Copy all information from Settings Help inside KeeWeb
render: shell
- type: textarea
id: description
attributes:
label: Description
description: |
Please provide a description of your issue here.
validations:
required: true
- type: textarea
id: steps-reproduce
attributes:
label: Steps To Reproduce
description: |
Describe the steps that need taken by the developer(s) to get the error / issue you're experiencing.
value: |
-
-
-
-
validations:
required: true
- type: textarea
id: expected-behavior
attributes:
label: Expected Behavior
description: |
Describe what you expect KeeWeb to actually do if it were working properly
value: |
-
-
-
-
validations:
required: true
- type: textarea
id: logs-console
attributes:
label: Logs
description: |
List any error messages you received in the developer console.
Developer console for **web** can be opened with **SHIFT + CTRL + I**
Open **dev tools** in your browser and attach output log from **CONSOLE** tab. If you are using a desktop app, devtools can be opened from Settings General Advanced.
render: shell
- type: textarea
id: references
attributes:
label: Referenced Issues
description: |
List any existing issues this ticket may be associated with.
Structure each issue as:
- #NUMBER
- #3
value: |
- #
- type: textarea
id: attachment-db
attributes:
label: "Attachments: Database kdbx File"
description: |
Attach a test database below that does give you the described issue.
Ensure it has no real security information within it as it will be publicly visible to all.
- type: textarea
id: attachment-screenshots
attributes:
label: "Attachments: Screenshots"
description: |
Please provide screenshots of any errors or the issue you're having.
Gifs are even better.

View File

@ -1,20 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: 'enhancement'
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@ -0,0 +1,45 @@
name: "💡 Feature Request"
description: Got a suggestion? Submit your request here.
title: "💡 Feature: <title>"
labels: [
"Type ◦ Feature"
]
body:
- type: markdown
attributes:
value: |
1. Please speak English.
2. Please take a moment to check that your feature hasn't already been suggested.
3. Be detailed but to the point.
- type: textarea
id: text-description
attributes:
label: Feature Description
description: |
Explain your feature. Be detailed.
If your feature addresses a problem; explain the problem.
placeholder: |
I would like to request ...
validations:
required: true
- type: textarea
id: text-alternatives
attributes:
label: Alternatives Considered
description: |
A clear and concise description of any alternative solutions or features you've considered.
validations:
required: false
- type: textarea
id: attachment-screenshots
attributes:
label: "Attachments: Screenshots"
description: |
If possible, provide screenshots.
Want a feature placed in a specific location? Mark it in a screenshot.
Want something modified? Try creating a mockup.
The more details about how it should look, the better.
Not required, but appreciated.

View File

@ -250,7 +250,7 @@ jobs:
tagRegex: "^v(\\d+\\.\\d+\\.\\d+)$"
tagRegexGroup: 1
- name: Setup GCloud
uses: google-github-actions/setup-gcloud@master
uses: google-github-actions/setup-gcloud@v0.7.0
with:
version: '285.0.0'
service_account_key: ${{ secrets.GCP_SA_KEY }}
@ -418,9 +418,9 @@ jobs:
- name: Purge CloudFlare cache
if: ${{ github.repository == 'keeweb/keeweb' }}
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CF_TOKEN: ${{ secrets.CF_TOKEN }}
run: |
curl -sS -X POST "https://api.cloudflare.com/client/v4/zones/dbda8e7e454d23edb08d4bc3b3f6f872/purge_cache" \
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
-H "Authorization: Bearer $CF_TOKEN" \
-H "Content-Type: application/json" \
--data '{"purge_everything":true}'

371
.github/workflows/deploy-cfp.yml vendored Normal file
View File

@ -0,0 +1,371 @@
# ---------------------------------------------------------------------------------------
# Cloudflare Pages Update Website
# Pushes a new version of the website to Cloudflare
#
# If editing this workflow, all you need to edit are the INPUT values and the global
# ENV variables. No need to go any deeper.
#
# CHANGE:
# - run-name && name
# - INPUTS:
# PROJECT_NAME
# - ENV:
# PROJECT_NAME
# DOMAIN
# DIR_BUILD_OUTPUT
# DIR_WORKING
# WRANGLER_VERSION
# BRANCH
# ---------------------------------------------------------------------------------------
run-name: "☁️ CF Deploy app.keeweb.info"
name: "☁️ CF app.keeweb.info"
# ---------------------------------------------------------------------------------------
# TRIGGERS
# ---------------------------------------------------------------------------------------
on:
push:
branches: [ master ]
workflow_dispatch:
inputs:
# ---------------------------------------------------------------------------------------
# The default values set for each input should not need to be changed as they are
# already set to the correct values.
# ---------------------------------------------------------------------------------------
PROJECT_NAME:
description: "Project Name"
required: true
default: 'app-keeweb'
type: string
DIRECTORY_BUILD_OUTPUT:
description: "Build Output Dir"
required: true
default: 'gh-pages'
type: string
DIRECTORY_WORKING:
description: "Root Dir"
required: true
default: './'
type: string
WRANGLER_VERSION:
description: "Wrangler Version"
required: true
default: '3'
type: string
BRANCH:
description: 'Website Branch'
required: true
default: 'gh-pages'
type: choice
options:
- gh-pages
# ---------------------------------------------------------------------------------------
# ENV VARIABLES
#
# PROJECT_NAME : This is the project name used in Cloudflare.
# DOMAIN : purely cosmetic which displays as the label of some steps
# ---------------------------------------------------------------------------------------
env:
PROJECT_NAME: ${{ github.event.inputs.PROJECT_NAME || 'app-keeweb' }}
DOMAIN: 'app.keeweb.info'
DIR_BUILD_OUTPUT: ${{ github.event.inputs.DIRECTORY_BUILD_OUTPUT || 'gh-pages' }}
DIR_WORKING: ${{ github.event.inputs.DIRECTORY_WORKING || './' }}
WRANGLER_VERSION: ${{ github.event.inputs.WRANGLER_VERSION || '2' }}
BRANCH: ${{ github.event.inputs.BRANCH || 'gh-pages' }}
# ---------------------------------------------------------------------------------------
# JOBS
# ---------------------------------------------------------------------------------------
jobs:
# ---------------------------------------------------------------------------------------
# JOB > PUBLISH
# ---------------------------------------------------------------------------------------
job-cfpublish:
name: >-
☁️ Publish Website
runs-on: ubuntu-latest
outputs:
upload_github: ${{ steps.task_cloudflare_project_create_github.outputs.SUCCESS }}
upload_assets: ${{ steps.task_cloudflare_project_create_assets.outputs.SUCCESS }}
existing_site: ${{ steps.task_cloudflare_project_check.outputs.EXISTING }}
permissions:
contents: read
deployments: write
id-token: write
steps:
- name: "✅ Start Publish"
id: task_cfpublish_start
run: |
echo "Publishing new version of ${{ env.DOMAIN }} to Cloudflare Pages service"
# ---------------------------------------------------------------------------------------
# Job > Publish > Checkout
# ---------------------------------------------------------------------------------------
- name: "☑️ Checkout gh-pages"
id: task_cfpublish_checkout
uses: actions/checkout@v4
with:
ref: gh-pages
path: gh-pages
fetch-depth: 0
# ---------------------------------------------------------------------------------------
# Job > Publish > Commit Dist
# used in original build script for keeweb
# ---------------------------------------------------------------------------------------
#- name: "Commit dist to gh-pages"
# working-directory: gh-pages
# run: |
# git rm -r '*'
# cp -r ../html/* .
# mkdir -p .github/workflows
# echo $GITHUB_SHA > build.txt
# date >> build.txt
# git add .
# git config --local user.email "action@github.com"
# git config --local user.name "GitHub Action"
# git commit -am v${{ steps.get_tag.outputs.tag }}
# ---------------------------------------------------------------------------------------
# Job > Publish > Push
# ---------------------------------------------------------------------------------------
#- name: "Push gh-pages"
# uses: keeweb/github-push-action@master
# with:
# github_token: ${{ secrets.GITHUB_TOKEN }}
# branch: gh-pages
# directory: gh-pages
# ---------------------------------------------------------------------------------------
# Job > Publish > Restore mtime
# ---------------------------------------------------------------------------------------
#- name: Restore git mtime
# working-directory: gh-pages
# run: python3 ../keeweb/.github/actions/scripts/git-restore-mtime.py
# ---------------------------------------------------------------------------------------
# CLOUDFLARE > NODE > SETUP
# ---------------------------------------------------------------------------------------
- name: "⚙️ Setup Node"
id: task_cfpublish_node_setup
uses: actions/setup-node@v4
with:
node-version: '20.x'
scope: '@aetherinox'
# ---------------------------------------------------------------------------------------
# CLOUDFLARE > NODE > INSTALL
# ---------------------------------------------------------------------------------------
- name: "📦 NPM Install Wrangler"
id: task_cloudflare_npm_install
run: |
npm install -g npm@latest
npm install --global wrangler
env:
NODE_AUTH_TOKEN: ${{ secrets.SELF_TOKEN_CL }}
# ---------------------------------------------------------------------------------------
# CLOUDFLARE > CHECK PROJECT
#
# this step checks to see if your project already exists on cloudflare
# ---------------------------------------------------------------------------------------
- name: "☁️ CF Check Project"
id: task_cloudflare_project_check
shell: bash
run: |
check=$(curl -s -X GET "https://api.cloudflare.com/client/v4/accounts/${{ secrets.CF_ACCOUNT_ID }}/pages/projects/${{ inputs.PROJECT_NAME || env.PROJECT_NAME }}" \
-H "Authorization: Bearer ${{ secrets.CF_TOKEN }}" \
-H "Content-Type:application/json" | jq -r '.success')
echo "EXISTING=$check" >> $GITHUB_OUTPUT
# ---------------------------------------------------------------------------------------
# CLOUDFLARE > CREATE PROJECT (LINKED TO GITHUB)
#
# This step will only success if you've already linked your Github account to cloudflare.
# if you attempt to push this deployment to any github repo that is not linked to
# cloudflare; the step will fail, and then execute step
# task_cloudflare_project_create_assets
# ---------------------------------------------------------------------------------------
- name: "☁️ CF Create Project Linked Github (if nonexistent)"
id: task_cloudflare_project_create_github
shell: bash
if: |
${{ steps.task_cloudflare_project_check.outputs.EXISTING != 'true' }}
run: |
check=$(curl -s -X POST "https://api.cloudflare.com/client/v4/accounts/${{ secrets.CF_ACCOUNT_ID }}/pages/projects" \
-H "Authorization: Bearer ${{ secrets.CF_TOKEN }}" \
-H "Content-Type:application/json" \
-d '{"name": "${{ inputs.PROJECT_NAME || env.PROJECT_NAME }}", "production_branch": "${{ inputs.BRANCH || env.BRANCH || 'master' }}", "source": {"type": "github", "config": {"owner": "${{ github.repository_owner }}", "repo_name": "${{ github.event.repository.name }}"}}}' | jq -r '.success')
echo "SUCCESS=$check" >> $GITHUB_OUTPUT
echo "Mode (Link Github): $check"
# ---------------------------------------------------------------------------------------
# CLOUDFLARE > CREATE PROJECT
#
# this step should execute only if the previous step failed.
# ---------------------------------------------------------------------------------------
- name: "☁️ CF Create Project Upload Assets (if nonexistent)"
id: task_cloudflare_project_create_assets
shell: bash
if: |
steps.task_cloudflare_project_check.outputs.EXISTING != 'true' && ( ${{ steps.task_cloudflare_project_create_github.outputs.SUCCESS == 'false' || failure()}} )
run: |
check=$(curl -s -X POST "https://api.cloudflare.com/client/v4/accounts/${{ secrets.CF_ACCOUNT_ID }}/pages/projects" \
-H "Authorization: Bearer ${{ secrets.CF_TOKEN }}" \
-H "Content-Type:application/json" \
-d '{"name":"${{ inputs.PROJECT_NAME || env.PROJECT_NAME }}", "production_branch":"${{ inputs.BRANCH || env.BRANCH || 'master' }}"}' | jq -r '.success')
echo "SUCCESS=$check" >> $GITHUB_OUTPUT
echo "Mode (Upload Assets): $check"
# ---------------------------------------------------------------------------------------
# CLOUDFLARE > CREATE > SUMMARY
#
# outlines which method was used to create the project
# ---------------------------------------------------------------------------------------
- name: "☁️ CF Create Project Summary"
id: task_cloudflare_project_summary
shell: bash
run: |
if [ ${{ steps.task_cloudflare_project_create_github.outputs.SUCCESS }} == 'true' ]; then
echo ""
echo "Project ${{ env.PROJECT_NAME }} linked to a Github account"
echo ""
elif [ ${{ steps.task_cloudflare_project_create_assets.outputs.SUCCESS }} == 'true' ]; then
echo "========================================================================="
echo ""
echo " Project ${{ env.PROJECT_NAME }} NOT linked to a Github account."
echo " Uploading assets to Cloudflare"
echo ""
echo "========================================================================="
echo "If you wish to sync the assets from Github to Cloudflare, you must ensure"
echo "you have connected your Github account to cloudflare and that you're"
echo "uploading from the same account that is linked."
elif [ ${{ steps.task_cloudflare_project_check.outputs.EXISTING }} == 'true' ]; then
echo ""
echo "Site already exists on Cloudflare, updating ${{ env.PROJECT_NAME }}"
echo ""
else
echo ""
echo "Error occured creating ${{ env.PROJECT_NAME }}"
echo ""
fi
# ---------------------------------------------------------------------------------------
# Job > Publish > Cloudflare Wrangler
# ---------------------------------------------------------------------------------------
#- name: "☁️ CF Publish KeeWeb.info"
# uses: cloudflare/wrangler-action@v3
# with:
# apiToken: ${{ secrets.CF_TOKEN }} # Cloudflare API Token at https://dash.cloudflare.com/profile/api-tokens
# accountId: ${{ secrets.CF_ACCOUNT_ID }} # Cloudflare account ID available on right side of CF website
# workingDirectory: ${{ inputs.DIRECTORY_WORKING || env.DIR_WORKING || './' }} # Working directory
# command: pages deploy ${{ inputs.DIRECTORY_BUILD_OUTPUT || env.DIR_BUILD_OUTPUT || './' }} --project-name=${{ inputs.PROJECT_NAME || env.PROJECT_NAME }} --commit-dirty=true
# ---------------------------------------------------------------------------------------
# Cloudflare Pages Action
#
# a new version has been supplied above to switch from
# cloudflare/pages-action@v1 -> cloudflare/wrangler-action@v3
# the new action uses NodeJS 20, instead of 16.
# ---------------------------------------------------------------------------------------
- name: "☁️ CF Publish ${{ env.DOMAIN }}"
uses: aetherinox/cloudflare-publish-action@latest
with:
apiToken: ${{ secrets.CF_TOKEN }} # Cloudflare API Token at https://dash.cloudflare.com/profile/api-tokens
accountId: ${{ secrets.CF_ACCOUNT_ID }} # Cloudflare account ID available on right side of CF website
projectName: ${{ inputs.PROJECT_NAME || env.PROJECT_NAME }} # Project name assigned at creation. view on workers-and-pages section of CF website
directory: ${{ inputs.DIRECTORY_BUILD_OUTPUT || env.DIR_BUILD_OUTPUT || './' }} # Output directory for built website
gitHubToken: ${{ secrets.SELF_TOKEN_CL }} # Optional: Enable this if you want to have GitHub Deployments triggered
branch: ${{ inputs.BRANCH || env.BRANCH || 'master' }} # Branch website published to; by default this will be the branch which triggered this workflow
workingDirectory: ${{ inputs.DIRECTORY_WORKING || env.DIR_WORKING || './' }} # Working directory
wranglerVersion: ${{ inputs.WRANGLER_VERSION || env.WRANGLER_VERSION || '3' }} # Optional: Change the Wrangler version, allows you to point to a specific version or a tag such as `beta`
# ---------------------------------------------------------------------------------------
# Cloudflare > Purge Cache
# ---------------------------------------------------------------------------------------
- name: "☁️ CF Purge Cache"
id: task_cloudflare_purge_cache
run: |
check=$(curl -s -X POST "https://api.cloudflare.com/client/v4/zones/${{ secrets.CF_ZONE_SITE_MAIN }}/purge_cache" \
-H "Authorization: Bearer ${{ secrets.CF_TOKEN }}" \
-H "Content-Type:application/json" \
-d '{"purge_everything":true}' | jq -r '.success')
echo "SUCCESS=$check" >> $GITHUB_OUTPUT
echo "Purge Cache: $check"
# ---------------------------------------------------------------------------------------
# JOB > COMPLETE
# ---------------------------------------------------------------------------------------
job-complete:
name: >-
🆗 Successful Deployment
needs: job-cfpublish
env:
UPLOAD_GITHUB: ${{ needs.job-cfpublish.outputs.upload_github }}
UPLOAD_ASSETS: ${{ needs.job-cfpublish.outputs.upload_assets }}
EXISTING_SITE: ${{ needs.job-cfpublish.outputs.existing_site }}
runs-on: ubuntu-latest
steps:
# ---------------------------------------------------------------------------------------
# Job > Complete > Get publish timestamp
# ---------------------------------------------------------------------------------------
- name: "🕛 Get Timestamp"
id: task_complete_timestamp_get
run: |
echo "NOW=$(date +'%m-%d-%Y %H:%M:%S')" >> $GITHUB_ENV
# ---------------------------------------------------------------------------------------
# Job > Complete > Summary of publish
# ---------------------------------------------------------------------------------------
- name: "🆗 CF Pages Deployment: ${{ env.DOMAIN }}"
id: task_complete_summary
run: |
echo ""
echo ""
echo "| Name | Result |" >> $GITHUB_STEP_SUMMARY
echo "| ------------------------------- | ----------------------- |" >> $GITHUB_STEP_SUMMARY
echo "| **Cloudflare Project ID** | ${{ env.PROJECT_NAME }} |" >> $GITHUB_STEP_SUMMARY
echo "| **Domain** | ${{ env.DOMAIN }} |" >> $GITHUB_STEP_SUMMARY
echo "| **Deploy Time** | ${{ env.NOW }} |" >> $GITHUB_STEP_SUMMARY
if [ ${{ env.EXISTING_SITE }} == 'true' ]; then
echo "| **Update Type** | 🟩 Update Existing |" >> $GITHUB_STEP_SUMMARY
else
if [ ${{ env.UPLOAD_GITHUB }} == 'true' ]; then
echo "| **Update Type** | 🇬 Create (Link Github) |" >> $GITHUB_STEP_SUMMARY
elif [ ${{ env.UPLOAD_ASSETS }} == 'true' ]; then
echo "| **Update Type** | 📦 Create (Asset Upload) |" >> $GITHUB_STEP_SUMMARY
else
echo "| **Update Type** | ❌ Could not push to Cloudflare |" >> $GITHUB_STEP_SUMMARY
fi
fi

31
.github/workflows/verify.yaml vendored Normal file
View File

@ -0,0 +1,31 @@
name: Verify
on:
push:
pull_request:
workflow_dispatch:
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version-file: '.nvmrc'
cache: 'npm'
- name: Install npm modules
run: npm ci
- name: Lint
run: npm run lint
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version-file: '.nvmrc'
cache: 'npm'
- name: Install npm modules
run: npm ci
- name: Test
run: npm test

1
.nvmrc Normal file
View File

@ -0,0 +1 @@
v16.20.2

View File

@ -16,7 +16,8 @@ Donate: [OpenCollective](https://opencollective.com/keeweb#support), [GitHub](ht
## Status
The app is quite stable now. Basic stuff, as well as more advanced operations, should be rather reliable.
The app is quite stable now. Basic stuff, as well as more advanced operations, should be rather reliable.
Looking for a new maintainer, see [#2022](https://github.com/keeweb/keeweb/issues/2022)
## Self-hosting

View File

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>KeeWeb</title>
</head>
<body>
<script>
window.opener.postMessage(
{ storage: 'teams', search: location.search },
window.location.origin
);
window.close();
</script>
</body>
</html>

View File

@ -258,7 +258,7 @@ const AutoType = {
if (entries.length === 0) {
if (evt.filter.useUrl) {
evt.filter.useUrl = false;
if (evt.filter.title) {
if (evt.filter.title && AppSettingsModel.autoTypeTitleFilterEnabled) {
evt.filter.useTitle = true;
}
}

View File

@ -209,6 +209,10 @@ const YubiKey = {
complete: (err, stdout) => {
this.process = null;
if (window.debugYubiKey) {
logger.info('received codes', err, stdout);
}
if (this.aborted) {
return callback('Aborted');
}

View File

@ -325,7 +325,7 @@ async function findEntry(request, returnIfOneMatch, filterOptions) {
if (!entries.length) {
if (AppSettingsModel.extensionFocusIfEmpty) {
filter.useUrl = false;
if (filter.title) {
if (filter.title && AppSettingsModel.autoTypeTitleFilterEnabled) {
filter.useTitle = true;
entries = filter.getEntries();
if (!entries.length) {

View File

@ -191,7 +191,7 @@ const Launcher = {
}
},
quitOnRealQuitEventIfMinimizeOnQuitIsEnabled() {
return !!(this.platform() === 'darwin' || this.pendingUpdateFile);
return !!this.pendingUpdateFile;
},
minimizeApp() {
this.remoteApp().minimizeApp({

View File

@ -37,4 +37,17 @@ const OneDriveApps = {
}
};
export { DropboxApps, GDriveApps, OneDriveApps };
const MsTeamsApps = {
Local: {
id: '8fbe2245-13d5-446f-bedc-74c3b2e1f635'
},
Production: {
id: '8fbe2245-13d5-446f-bedc-74c3b2e1f635'
},
Desktop: {
id: '8fbe2245-13d5-446f-bedc-74c3b2e1f635',
secret: 'F02~HYaWs-~7MndJcVRtv9~h-50Brk_9ho'
}
};
export { DropboxApps, GDriveApps, OneDriveApps, MsTeamsApps };

View File

@ -18,6 +18,7 @@ const DefaultAppSettings = {
colorfulIcons: false, // use colorful custom icons instead of grayscale
useMarkdown: true, // use Markdown in Notes field
directAutotype: true, // if only one matching entry is found, select that one automatically
autoTypeTitleFilterEnabled: true, // enable the title filtering in auto-type by default
titlebarStyle: 'default', // window titlebar style
lockOnMinimize: true, // lock the app when it's minimized
lockOnCopy: false, // lock the app after a password was copied
@ -86,7 +87,13 @@ const DefaultAppSettings = {
onedrive: true, // enable OneDrive integration
onedriveClientId: null, // custom OneDrive client id
onedriveClientSecret: null // custom OneDrive client secret
onedriveClientSecret: null, // custom OneDrive client secret
onedriveTenantId: null, // custom OneDrive tenant id
msteams: false, // enable Microsoft Teams integration
msteamsClientId: null, // custom Microsoft Teams client id
msteamsClientSecret: null, // custom Microsoft Teams client secret
msteamsTenantId: null // custom Microsoft Teams tenant id
};
export { DefaultAppSettings };

View File

@ -23,17 +23,14 @@ const Links = {
HaveIBeenPwnedPrivacy: 'https://haveibeenpwned.com/Passwords',
ExtensionHelpForOtherBrowsers:
'https://github.com/keeweb/keeweb/wiki/Browser-AutoFill#other-browsers',
ExtensionHelpForKPXC:
'https://github.com/keeweb/keeweb/wiki/Browser-AutoFill#keepassxc-browser',
KWCForChrome:
'https://chrome.google.com/webstore/detail/keeweb-connect/pikpfmjfkekaeinceagbebpfkmkdlcjk',
KWCForFirefox: 'https://addons.mozilla.org/firefox/addon/keeweb-connect/',
KWCForEdge:
'https://microsoftedge.microsoft.com/addons/detail/keewebconnect/nmggpehkjmeaeocmaijenpejbepckinm',
KWCForSafari: 'https://apps.apple.com/app/keeweb-connect/id1565748094',
KPXCForChrome:
'https://chrome.google.com/webstore/detail/keepassxc-browser/oboonakemofpalcgghocfoadofidjkkk',
KPXCForFirefox: 'https://addons.mozilla.org/firefox/addon/keepassxc-browser/',
KPXCForEdge:
'https://microsoftedge.microsoft.com/addons/detail/keepassxcbrowser/pdffhmdngciaglkoonimfcmckehcpafo'
KWCForSafari: 'https://apps.apple.com/app/keeweb-connect/id1565748094'
};
export { Links };

View File

@ -43,6 +43,7 @@
"dropbox": "Dropbox",
"gdrive": "Google Drive",
"onedrive": "OneDrive",
"msteams": "Microsoft Teams",
"menuAllItems": "All Items",
"menuColors": "Colors",
"menuTrash": "Trash",
@ -432,7 +433,8 @@
"setGenColorfulIcons": "Colorful custom icons in the list",
"setGenUseMarkdown": "Markdown in notes",
"setGenUseGroupIconForEntries": "Automatically use group icon for new entries",
"setGenDirectAutotype": "If only one matching entry is found, select that one automatically for Autotype",
"setGenDirectAutotype": "If only one matching entry is found, select that one automatically for auto-type",
"setGenAutoTypeTitleFilterEnabled": "Filter entries by title in auto-type",
"setGenFunction": "Function",
"setGenAutoSyncOnClose": "Automatically save and sync on close",
"setGenAutoSyncTimer": "Automatically save and sync periodically",

View File

@ -14,6 +14,7 @@
"or": "ou",
"history": "historique",
"template": "modèle",
"templates": "templates",
"notImplemented": "Non Implémenté",
"saveChanges": "Sauvegarder les modifications",
"discardChanges": "Annuler les modifications",
@ -46,6 +47,7 @@
"menuColors": "Couleurs",
"menuTrash": "Corbeille",
"menuSetGeneral": "Général",
"menuSetBrowser": "Navigateur",
"menuSetAbout": "À propos",
"menuSetDevices": "Appareils",
"menuAlertNoTags": "Aucun tag",
@ -60,7 +62,7 @@
"sysMenuServices": "Services",
"sysMenuHide": "Cacher {}",
"sysMenuHideOthers": "Cacher autres",
"sysMenuUnhide": "Montrer tous",
"sysMenuUnhide": "Montrer tout",
"sysMenuQuit": "Quitter {}",
"sysMenuEdit": "Editer",
"sysMenuUndo": "Annuler",
@ -80,6 +82,8 @@
"alertCopy": "Copier",
"alertClose": "Fermer",
"alertDoNotAsk": "Ne plus me le redemander",
"alertAllow": "Autoriser",
"alertDeny": "Refuser",
"appBeta": "ATTENTION: version beta, aperçu seulement",
"footerOpen": "Ouvrir/Nouveau",
"footerSyncError": "Erreur de synchronisation",
@ -139,6 +143,7 @@
"keyChangeMessageExpired": "La clé maître pour cette base de donnée est expirée. Merci de saisir une nouvelle clé.",
"keyChangeRepeatPassword": "Mot de passe, encore une fois",
"keyEnter": "Entrée",
"keyEsc": "Esc",
"iconFavTitle": "Télécharger et utiliser le favicon du site web",
"iconSelCustom": "Sélectionner une icône personnalisée",
"listEmptyTitle": "Vide",
@ -267,6 +272,7 @@
"detMore": "plus",
"detClickToAddField": "cliquez pour ajouter un nouveau champ",
"detMenuAddNewField": "Ajouter nouveau champ",
"detMenuAddNewWebsite": "Ajouter un autre site",
"detMenuShowEmpty": "Montrer les champs vides",
"detMenuHideEmpty": "Cacher les champs vides",
"detMenuAddField": "Ajouter {}",
@ -340,6 +346,7 @@
"autoTypeSelectionHintOpt": "Entrez seulement le compte",
"autoTypeSelectionHintShift": "Autres champs",
"autoTypeSelectionOtp": "code à usage unique",
"autoTypeUnlockMessage": "Déverrouiller pour saisir automatiquement",
"appSecWarn": "Non sécurisé !",
"appSecWarnBody1": "Vous avez chargé cette appli avec une connexion non sécurisée. Quelqu'un peut vous observer et voler vos mots de passe. Nous vous recommandons fortement d'arrêter cette connexion, à moins que vous ne compreniez exactement ce que vous faites.",
"appSecWarnBody2": "Oui, votre base est chiffrée mais personne ne peut garantir que l'application n'a pas été modifiée avant d'arriver à vous.",
@ -614,6 +621,38 @@
"setPlTranslateLink": "traduire l'appli dans votre langue",
"setPlAutoUpdate": "Mise à jour automatique",
"setPlLoadGallery": "Charger le catalogue",
"setBrowserTitle": "Navigateur",
"setBrowserIntroDesktop": "KeeWeb peut entrer des mots de passe en utilisant la saisie automatique, cependant, une extension peut être un moyen plus pratique de le faire. KeeWeb prend en charge deux extensions de navigateur:",
"setBrowserIntroKeeWebConnect": "l'extension officielle KeeWeb est construite avec les fonctionnalités de KeeWeb, mais vous risquez de manquer certaines fonctionnalités avancées que l'on peut trouver dans l'autre extension.",
"setBrowserIntroKeePassXcBrowser": "cette extension a été développée pour KeePassXC, elle existe depuis un moment et est assez fiable. L'extension ne fait pas partie de KeeWeb et il peut y avoir des problèmes de compatibilité.",
"setBrowserIntroWeb": "Installez notre extension de navigateur pour remplir automatiquement les mots de passe de KeeWeb sur différentes pages. L'extension de navigateur se connectera à un onglet KeeWeb de votre navigateur pour récupérer les mots de passe. Cliquez ici pour télécharger l'extension:",
"setBrowserNotEnabled": "L'intégration du navigateur n'est pas activée, les extensions ne pourront pas se connecter à KeeWeb. Utilisez les cases à cocher ci-dessous pour l'activer:",
"setBrowserEnablePerBrowser": "Activer l'intégration dans le navigateur en cochant ci dessous:",
"setBrowserFocusIfLocked": "Ouvrir KeeWeb si le navigateur essaye de se connecter alors que aucun fichier n'est ouvert",
"setBrowserFocusIfEmpty": "Afficher la liste si aucune correspondance n'est trouvée avec l'URL",
"setBrowserOtherBrowsers": "Autres navigateurs",
"setBrowserExtensionFor": "pour {}",
"setBrowserExtensionNotSupported": "Non supporté",
"setBrowserExtensionHelp": "Comment installer ?",
"setBrowserExtensionInstall": "Installer l'application",
"setBrowserExtensionKPXCWarnHeader": "{} cessera de fonctionner",
"setBrowserExtensionKPXCWarnBody1": "Malheureusement, il n'est pas possible de connecter une extension à plusieurs applications. Si vous connectez l'extension à KeeWeb, nous remplacerons son association d'application, ce qui signifie que l'intégration avec {} cessera de fonctionner. Même si vous décochez cette case, l'association avec {} ne sera pas restaurée. Pour le faire fonctionner à nouveau, configurez l'intégration du navigateur dans les paramètres de {}.",
"setBrowserExtensionKPXCWarnBody2": "Configurer l'extension pour utiliser KeeWeb?",
"setBrowserSessions": "Sessions",
"setBrowserSessionsEmpty": "Aucune session connectée",
"setBrowserSessionsIntro": "Ces extensions sont connectées à KeeWeb",
"setBrowserSessionsActiveTooltip": "Session active",
"setBrowserSessionsActiveText": "Cette session est active. Elle peut échanger des données avec KeeWeb selon les permissions:",
"setBrowserSessionsInactiveTooltip": "session inactive",
"setBrowserSessionsInactiveText": "Cette session est inactive. L'extension est connectée à KeeWeb, cependant, elle n'a pas essayé d'échanger des données. Lorsque l'extension demande quelque chose, vous pourrez choisir ce que vous souhaitez partager.",
"setBrowserSessionsDeniedTooltip": "Accès interdit",
"setBrowserSessionsDeniedText": "Cette session est inactive. L'extension est connectée à KeeWeb mais vous avez refusé l'accès aux données.",
"setBrowserSessionsConnectedDate": "Connecté",
"setBrowserSessionsTerminate": "Clôturer la session",
"setBrowserSessionsAccessToFiles": "Autoriser l'accès aux fichiers",
"setBrowserSessionsNoFileAccess": "L'extension n'a accès à aucun fichier, elle ne pourra pas récupérer les mots de passe de KeeWeb.",
"setBrowserSessionsPasswordsRead": "Accès aux mot de passe",
"setBrowserSessionsPasswordsWritten": "Mots de passe enregistrés",
"setDevicesTitle": "Appareils",
"setDevicesEnableUsb": "Autoriser les interactions avec les appareils USB",
"setDevicesYubiKeyIntro": "Il est recommandé de lire {} avant d'utiliser une YubiKey.",
@ -648,12 +687,12 @@
"setHelpProblems2": "ou {}",
"setHelpOpenIssue": "faire connaître le problème sur GitHub",
"setHelpContactLink": "contactez le développeur directement",
"setHelpAppInfo": "Information",
"setHelpAppInfo": "Informations",
"setHelpOtherPlatforms": "Autres plateformes",
"setHelpDesktopApps": "Applis Desktop",
"setHelpWebApp": "Appli web",
"setHelpUpdates": "Mises à jour",
"setHelpTwitter": "Appli twitter",
"setHelpTwitter": "Twitter",
"dropboxSetupDesc": "Une configuration particulière est nécessaire pour utiliser Dropbox dans une application auto-hébergée. Merci de créer votre propre application Dropbox et d'inscrire sa clé ci-dessous.",
"dropboxAppKey": "Clé Dropbox",
"dropboxAppKeyDesc": "Copier la clé de votre appli Dropbox (Réglages développeur)",
@ -669,6 +708,8 @@
"dropboxLinkFull": "Tout Dropbox ou n'importe quel dossier",
"dropboxLinkCustom": "Votre appli Dropbox",
"gdriveSharedWithMe": "Partagé avec moi",
"gdriveSharedDrives": "Lecteurs partagés",
"gdriveTeamDrives": "Team drives",
"webdavSaveMethod": "Méthode de sauvegarde",
"webdavSaveMove": "Envoyer un fichier temporaire et le déplacer",
"webdavSavePut": "Écraser le fichier kdbx avec PUT",
@ -697,5 +738,39 @@
"yubiKeyDisabledErrorHeader": "L'USB est désactivé",
"yubiKeyDisabledErrorBody": "Yubikey est nécessaire pour ouvrir ce fichier, merci d'activer les appareils USB dans les paramètres",
"yubiKeyErrorWithCode": "Erreur Yubikey code {}.",
"bioOpenAuthPrompt": "ouvrir \"{}\""
"bioOpenAuthPrompt": "ouvrir \"{}\"",
"extensionErrorNoOpenFiles": "Aucun fichier ouvert",
"extensionErrorUserRejected": "La requête a été refusée",
"extensionErrorNoMatches": "Aucune correspondance",
"extensionErrorAlertDisplayed": "Impossible de poser une question maintenant car une autre boîte de dialogue est affichée, veuillez réessayer",
"extensionConnectHeader": "Échange de données avec l'extension",
"extensionConnectIntro": "Une extension de navigateur qui a pour nom {} essaie d'échanger des données avec KeeWeb.",
"extensionConnectUnknownActivity": "KeeWeb ne vérifie pas que l'application connectée est ce qu'elle prétend être. N'approuvez la demande que si vous en connaissez l'origine.",
"extensionConnectFiles": "Lors de cette session, autoriser l'accès à:",
"extensionConnectAllOtherFiles": "Tous les autres fichiers",
"extensionConnectAllFiles": "Tous les fichiers",
"extensionConnectAskGet": "Demandez avant d'envoyer les mots de passe à l'extension:",
"extensionConnectAskGetMultiple": "s'il y a plus d'une correspondance",
"extensionConnectAskGetAlways": "Toujours",
"extensionConnectAskSave": "Demandez avant d'enregistrer de nouveaux mots de passe dans KeeWeb:",
"extensionConnectAskSaveAlways": "Toujours",
"extensionConnectAskSaveAuto": "quand ce n'est pas possible de sauvegarder automatiquement",
"extensionConnectSettingsAreForSession": "Les paramètres que vous sélectionnez ici ne sont valides que pour la session en cours. Vous pouvez afficher et gérer les sessions dans les paramètres de KeeWeb.",
"extensionUnlockMessage": "Déverrouiller pour connecter l'extension du navigateur",
"extensionNewGroupHeader": "Nouveau groupe",
"extensionNewGroupBody": "{} essaye de créer un nouveau groupe. Autoriser cela?",
"extensionNewGroupPath": "Dossier du groupe",
"extensionNewGroupFile": "Ce groupe sera créé dans:",
"extensionSaveEntryHeader": "Sauvegarder mot de passe",
"extensionSaveEntryBody": "{} essaye de sauvegarder un mot de passe. Autoriser cela?",
"extensionSaveEntryAuto": "Sauvegarder les autres mots de passe automatiquement lors de cette session",
"extensionSaveEntryNewGroup": "nouveau groupe",
"extensionSelectPasswordFor": "Sélectionner un mot de passe pour {}",
"selectEntryHeader": "Sélectionner une entrée",
"selectEntryEnterHint": "Utiliser la ligne sélectionnée",
"selectEntryTypingHint": "Ecrire pour filtrer",
"selectEntryContains": "Contient le texte",
"selectEntrySubdomains": "Sous-domaines",
"selectEntryFieldHeader": "Sélectionnez un champ",
"selectEntryFieldTouch": "Appuyez sur le bouton de votre appareil pour générer un code à usage unique."
}

View File

@ -467,6 +467,7 @@ class EntryModel extends Model {
if (otpUrl.isProtected) {
otpUrl = otpUrl.getText();
}
// called only if secret provided, no formatted url
if (Otp.isSecret(otpUrl.replace(/\s/g, ''))) {
otpUrl = Otp.makeUrl(otpUrl.replace(/\s/g, '').toUpperCase());
} else if (otpUrl.toLowerCase().lastIndexOf('otpauth:', 0) !== 0) {

View File

@ -536,7 +536,7 @@ class FileModel extends Model {
}
removeKeyFile() {
this.db.credentials.keyFileHash = null;
this.db.credentials.setKeyFile(null);
const changed = !!this.oldKeyFileHash;
if (!changed && this.db.credentials.passwordHash === this.oldPasswordHash) {
this.db.meta.keyChanged = this.oldKeyChangeDate;
@ -677,10 +677,10 @@ class FileModel extends Model {
addCustomIcon(iconData) {
const uuid = kdbxweb.KdbxUuid.random();
this.db.meta.customIcons[uuid] = {
this.db.meta.customIcons.set(uuid.id, {
data: kdbxweb.ByteUtils.arrayToBuffer(kdbxweb.ByteUtils.base64ToBytes(iconData)),
lastModified: new Date()
};
});
return uuid.toString();
}

View File

@ -221,22 +221,30 @@ class StorageOneDrive extends StorageBase {
_getOAuthConfig() {
let clientId = this.appSettings.onedriveClientId;
let clientSecret = this.appSettings.onedriveClientSecret;
let tenant = this.appSettings.onedriveTenantId;
if (!clientId) {
if (Features.isDesktop) {
({ id: clientId, secret: clientSecret } = OneDriveApps.Desktop);
({ id: clientId, secret: clientSecret, tenantId: tenant } = OneDriveApps.Desktop);
} else if (Features.isLocal) {
({ id: clientId, secret: clientSecret } = OneDriveApps.Local);
({ id: clientId, secret: clientSecret, tenantId: tenant } = OneDriveApps.Local);
} else {
({ id: clientId, secret: clientSecret } = OneDriveApps.Production);
({
id: clientId,
secret: clientSecret,
tenantId: tenant
} = OneDriveApps.Production);
}
}
tenant = tenant || 'common';
let scope = 'files.readwrite';
if (!this.appSettings.shortLivedStorageToken) {
scope += ' offline_access';
}
return {
url: 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize',
tokenUrl: 'https://login.microsoftonline.com/common/oauth2/v2.0/token',
url: `https://login.microsoftonline.com/${tenant}/oauth2/v2.0/authorize`,
tokenUrl: `https://login.microsoftonline.com/${tenant}/oauth2/v2.0/token`,
scope,
clientId,
clientSecret,

View File

@ -0,0 +1,365 @@
import { StorageBase } from 'storage/storage-base';
import { MsTeamsApps } from 'const/cloud-storage-apps';
import { Features } from 'util/features';
// https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow
// https://graph.microsoft.com/v1.0/me/transitiveMemberOf/microsoft.graph.group?$count=true
// https://graph.microsoft.com/v1.0/groups?$filter=groupTypes/any(c:c+eq+'Unified')
// https://graph.microsoft.com/v1.0/groups?$filter=groupTypes/any(c:c+eq+'Unified')&$orderby=displayName
// https://graph.microsoft.com/v1.0/groups?$orderby=displayName
// /me/joinedTeams
// https://graph.microsoft.com/v1.0/groups/{group id}/drive/root/children
class StorageTeams extends StorageBase {
name = 'msteams';
enabled = true;
uipos = 50;
icon = 'user-friends';
_graphUrl = 'https://graph.microsoft.com/v1.0';
_groupsUrl = `${this._graphUrl}/me/joinedTeams`;
_baseUrl = `${this._graphUrl}/groups`;
getPathForName(fileName) {
return '/drive/root:/' + fileName + '.kdbx';
}
genUrlAddress(groupId, path) {
if (groupId) {
return this._baseUrl + '/' + groupId + (path ? '/' + path.replace(/^\/+/, '') : '');
} else {
return this._groupsUrl;
}
}
genUrl(path) {
if (!path) {
const groupId = null;
const dir = null;
const url = this.genUrlAddress(groupId, dir);
return [groupId, dir, url];
}
const parts = path.replace(/^\/+/, '').split('/');
if (parts.length === 0) {
const groupId = null;
const dir = null;
const url = this.genUrlAddress(groupId, dir);
return [groupId, dir, url];
} else if (parts.length === 1) {
const groupId = parts[0];
const dir = null;
const url = this.genUrlAddress(groupId, dir);
return [groupId, dir, url];
} else {
path = path.replace(/\/drive\/root\:/, '');
const groupId = parts[0];
const dir = ('/' + parts.slice(1).join('/')).replace(/^\/+/, '');
const url = this.genUrlAddress(groupId, dir);
return [groupId, dir, url];
}
}
load(path, opts, callback) {
this._oauthAuthorize((err) => {
if (err) {
return callback && callback(err);
}
this.logger.debug('Load', path);
const ts = this.logger.ts();
const urlParts = this.genUrl(path);
const groupId = urlParts[0];
path = urlParts[1];
const url = urlParts[2];
if (!groupId) {
const err = 'no group id defined';
return callback && callback(err);
}
this._xhr({
url,
responseType: 'json',
success: (response) => {
const downloadUrl = response['@microsoft.graph.downloadUrl'];
let rev = response.eTag;
if (!downloadUrl || !response.eTag) {
this.logger.debug(
'Load error',
path,
'no download url',
response,
this.logger.ts(ts)
);
return callback && callback('no download url');
}
this._xhr({
url: downloadUrl,
responseType: 'arraybuffer',
skipAuth: true,
success: (response, xhr) => {
rev = xhr.getResponseHeader('ETag') || rev;
this.logger.debug('Loaded', path, rev, this.logger.ts(ts));
return callback && callback(null, response, { rev });
},
error: (err) => {
this.logger.error('Load error', path, err, this.logger.ts(ts));
return callback && callback(err);
}
});
},
error: (err) => {
this.logger.error('Load error', path, err, this.logger.ts(ts));
return callback && callback(err);
}
});
});
}
stat(path, opts, callback) {
this._oauthAuthorize((err) => {
if (err) {
return callback && callback(err);
}
this.logger.debug('Stat', path);
const ts = this.logger.ts();
const urlParts = this.genUrl(path);
const groupId = urlParts[0];
path = urlParts[1];
const url = urlParts[2];
if (!groupId) {
const err = 'no group id defined';
return callback && callback(err);
}
this._xhr({
url,
responseType: 'json',
success: (response) => {
const rev = response.eTag;
if (!rev) {
this.logger.error('Stat error', path, 'no eTag', this.logger.ts(ts));
return callback && callback('no eTag');
}
this.logger.debug('Stated', path, rev, this.logger.ts(ts));
return callback && callback(null, { rev });
},
error: (err, xhr) => {
if (xhr.status === 404) {
this.logger.debug('Stated not found', path, this.logger.ts(ts));
return callback && callback({ notFound: true });
}
this.logger.error('Stat error', path, err, this.logger.ts(ts));
return callback && callback(err);
}
});
});
}
save(path, opts, data, callback, rev) {
this._oauthAuthorize((err) => {
if (err) {
return callback && callback(err);
}
this.logger.debug('Save', path, rev);
const ts = this.logger.ts();
const urlParts = this.genUrl(path);
const groupId = urlParts[0];
path = urlParts[1];
const url = urlParts[2] + ':/content';
if (!groupId) {
const err = 'no group id defined';
return callback && callback(err);
}
this._xhr({
url,
method: 'PUT',
responseType: 'json',
headers: rev ? { 'If-Match': rev } : null,
data,
statuses: [200, 201, 412],
success: (response, xhr) => {
rev = response.eTag;
if (!rev) {
this.logger.error('Save error', path, 'no eTag', this.logger.ts(ts));
return callback && callback('no eTag');
}
if (xhr.status === 412) {
this.logger.debug('Save conflict', path, rev, this.logger.ts(ts));
return callback && callback({ revConflict: true }, { rev });
}
this.logger.debug('Saved', path, rev, this.logger.ts(ts));
return callback && callback(null, { rev });
},
error: (err) => {
this.logger.error('Save error', path, err, this.logger.ts(ts));
return callback && callback(err);
}
});
});
}
list(dir, callback) {
this._oauthAuthorize((err) => {
if (err) {
return callback && callback(err);
}
this.logger.debug('List', dir);
const ts = this.logger.ts();
const urlParts = this.genUrl(dir);
const groupId = urlParts[0];
dir = urlParts[1];
const urlPath = groupId ? (dir ? ':/children' : '/drive/root/children') : '';
const url = urlParts[2] + urlPath;
const self = this;
self._groupId = groupId;
this._xhr({
url,
responseType: 'json',
success: (response) => {
if (!response || !response.value) {
this.logger.error('List error', this.logger.ts(ts), response);
return callback && callback('list error');
}
this.logger.debug('Listed', this.logger.ts(ts));
let fileList;
if (!self._groupId) {
fileList = response.value
.filter((f) => f.displayName)
.map((f) => ({
name: f.displayName,
path: '/' + f.id,
rev: f.id,
dir: true
}));
} else {
fileList = response.value
.filter((f) => f.name)
.map((f) => ({
name: f.name,
path: `/${self._groupId}${f.parentReference.path}/${f.name}`,
rev: f.eTag,
dir: !!f.folder
}));
}
return callback && callback(null, fileList);
},
error: (err) => {
this.logger.error('List error', this.logger.ts(ts), err);
return callback && callback(err);
}
});
});
}
remove(path, callback) {
this.logger.debug('Remove', path);
const ts = this.logger.ts();
const urlParts = this.genUrl(path);
const groupId = urlParts[0];
path = urlParts[1];
const url = urlParts[2];
if (!groupId) {
const err = 'no group id defined';
return callback && callback(err);
}
this._xhr({
url,
method: 'DELETE',
responseType: 'json',
statuses: [200, 204],
success: () => {
this.logger.debug('Removed', path, this.logger.ts(ts));
return callback && callback();
},
error: (err) => {
this.logger.error('Remove error', path, err, this.logger.ts(ts));
return callback && callback(err);
}
});
}
mkdir(path, callback) {
this._oauthAuthorize((err) => {
if (err) {
return callback && callback(err);
}
this.logger.debug('Make dir', path);
const ts = this.logger.ts();
const urlParts = this.genUrl(path);
const groupId = urlParts[0];
path = urlParts[1];
const url = urlParts[2] + '/drive/root/children';
if (!groupId) {
const err = 'no group id defined';
return callback && callback(err);
}
const data = JSON.stringify({ name: path.replace('/drive/root:/', ''), folder: {} });
this._xhr({
url,
method: 'POST',
responseType: 'json',
statuses: [200, 204],
data,
dataType: 'application/json',
success: () => {
this.logger.debug('Made dir', path, this.logger.ts(ts));
return callback && callback();
},
error: (err) => {
this.logger.error('Make dir error', path, err, this.logger.ts(ts));
return callback && callback(err);
}
});
});
}
logout(enabled) {
this._oauthRevokeToken();
}
_getOAuthConfig() {
let clientId = this.appSettings.msteamsClientId;
let clientSecret = this.appSettings.msteamsClientSecret;
let tenant = this.appSettings.msteamsTenantId;
if (!clientId) {
if (Features.isDesktop) {
({ id: clientId, secret: clientSecret, tenantId: tenant } = MsTeamsApps.Desktop);
} else if (Features.isLocal) {
({ id: clientId, secret: clientSecret, tenantId: tenant } = MsTeamsApps.Local);
} else {
({ id: clientId, secret: clientSecret, tenantId: tenant } = MsTeamsApps.Production);
}
}
tenant = tenant || 'common';
let scope = 'Sites.ReadWrite.All Team.ReadBasic.All';
if (!this.appSettings.shortLivedStorageToken) {
scope += ' offline_access';
}
return {
url: `https://login.microsoftonline.com/${tenant}/oauth2/v2.0/authorize`,
tokenUrl: `https://login.microsoftonline.com/${tenant}/oauth2/v2.0/token`,
scope,
clientId,
clientSecret,
pkce: true,
width: 600,
height: 500
};
}
}
export { StorageTeams };

View File

@ -5,6 +5,7 @@ import { StorageFile } from 'storage/impl/storage-file';
import { StorageFileCache } from 'storage/impl/storage-file-cache';
import { StorageGDrive } from 'storage/impl/storage-gdrive';
import { StorageOneDrive } from 'storage/impl/storage-onedrive';
import { StorageTeams } from 'storage/impl/storage-teams';
import { StorageWebDav } from 'storage/impl/storage-webdav';
import { createOAuthSession } from 'storage/pkce';
@ -17,6 +18,7 @@ const ThirdPartyStorage = {
dropbox: new StorageDropbox(),
gdrive: new StorageGDrive(),
onedrive: new StorageOneDrive(),
msteams: new StorageTeams(),
webdav: new StorageWebDav()
};

View File

@ -12,7 +12,7 @@ class CsvParser {
this.result = [];
this.next = this.handleBeforeValue;
this.index = 0;
while (this.next && this.index < this.csv.length) {
while (this.next && this.index <= this.csv.length) {
this.next = this.next(this);
}
if (this.lines.length <= 1) {

View File

@ -135,12 +135,12 @@ Otp.leftPad = function (str, len) {
};
Otp.parseUrl = function (url) {
const match = /^otpauth:\/\/(\w+)\/([^\?]+)\?(.*)/i.exec(url);
const match = /^otpauth:\/\/(\w+)(?:\/([^\?]+)\?|\?)(.*)/i.exec(url);
if (!match) {
throw 'Not OTP url';
}
const params = {};
const label = decodeURIComponent(match[2]);
const label = decodeURIComponent(match[2] ?? 'default');
if (label) {
const parts = label.split(':');
params.issuer = parts[0].trim();
@ -148,7 +148,8 @@ Otp.parseUrl = function (url) {
params.account = parts[1].trim();
}
}
params.type = match[1].toLowerCase();
params.type = match[1].toLowerCase(); // returns "totp"
// match[3] = secret=XXXXXXXXXXXXX&period=30&digits=6&algorithm=SHA1
match[3].split('&').forEach((part) => {
const parts = part.split('=', 2);
params[parts[0].toLowerCase()] = decodeURIComponent(parts[1]);

View File

@ -8,6 +8,11 @@ class RandomNameGenerator {
}
function charCodeToHtml(char) {
// convert certain special chars like space into to non-breaking space
// ' ' to &#nbsp;
if (char === 32 || char === 8193 || char === 8239) {
char = 160;
}
return Math.random() < 0.2 ? String.fromCharCode(char) : `&#x${char.toString(16)};`;
}

View File

@ -6,8 +6,8 @@ const ExpectedFieldRefByteLength = ExpectedFieldRefChars.length;
kdbxweb.ProtectedValue.prototype.isProtected = true;
kdbxweb.ProtectedValue.prototype.forEachChar = function (fn) {
const value = this._value;
const salt = this._salt;
const value = this.value;
const salt = this.salt;
let b, b1, b2, b3;
for (let i = 0, len = value.length; i < len; i++) {
b = value[i] ^ salt[i];
@ -154,7 +154,7 @@ kdbxweb.ProtectedValue.prototype.equals = function (other) {
return false;
}
for (let i = 0; i < len; i++) {
if ((this._value[i] ^ this._salt[i]) !== (other._value[i] ^ other._salt[i])) {
if ((this.value[i] ^ this.salt[i]) !== (other.value[i] ^ other.salt[i])) {
return false;
}
}
@ -181,8 +181,8 @@ kdbxweb.ProtectedValue.prototype.saltedValue = function () {
if (!this.byteLength) {
return 0;
}
const value = this._value;
const salt = this._salt;
const value = this.value;
const salt = this.salt;
let salted = '';
for (let i = 0, len = value.length; i < len; i++) {
const byte = value[i] ^ salt[i];
@ -193,8 +193,8 @@ kdbxweb.ProtectedValue.prototype.saltedValue = function () {
kdbxweb.ProtectedValue.prototype.dataAndSalt = function () {
return {
data: [...this._value],
salt: [...this._salt]
data: [...this.value],
salt: [...this.salt]
};
};

View File

@ -57,7 +57,7 @@ class SettingsBrowserView extends View {
} else {
const extensionBrowserFamily = Features.extensionBrowserFamily;
data.extensionBrowserFamily = Features.extensionBrowserFamily;
data.extensionDownloadLink = Links[`KeeWebConnectFor${extensionBrowserFamily}`];
data.extensionDownloadLink = Links[`KWCFor${extensionBrowserFamily}`];
}
super.render(data);
}
@ -72,6 +72,9 @@ class SettingsBrowserView extends View {
enabled: !!AppSettingsModel[`extensionEnabled${ext.alias}${browser}`],
installUrl: Links[`${ext.alias}For${browser}`]
};
if (ext.alias === 'KPXC') {
ext.manualUrl = Links.ExtensionHelpForKPXC;
}
if (!ext.installUrl) {
if (browser === 'Other') {
ext.helpUrl = Links.ExtensionHelpForOtherBrowsers;

View File

@ -53,6 +53,7 @@ class SettingsGeneralView extends View {
'change .settings__general-use-markdown': 'changeUseMarkdown',
'change .settings__general-use-group-icon-for-entries': 'changeUseGroupIconForEntries',
'change .settings__general-direct-autotype': 'changeDirectAutotype',
'change .settings__general-autotype-title-filter': 'changeAutoTypeTitleFilter',
'change .settings__general-field-label-dblclick-autotype':
'changeFieldLabelDblClickAutoType',
'change .settings__general-device-owner-auth': 'changeDeviceOwnerAuth',
@ -135,6 +136,7 @@ class SettingsGeneralView extends View {
useMarkdown: AppSettingsModel.useMarkdown,
useGroupIconForEntries: AppSettingsModel.useGroupIconForEntries,
directAutotype: AppSettingsModel.directAutotype,
autoTypeTitleFilterEnabled: AppSettingsModel.autoTypeTitleFilterEnabled,
fieldLabelDblClickAutoType: AppSettingsModel.fieldLabelDblClickAutoType,
supportsTitleBarStyles: Features.supportsTitleBarStyles,
supportsCustomTitleBarAndDraggableWindow:
@ -431,6 +433,11 @@ class SettingsGeneralView extends View {
AppSettingsModel.directAutotype = directAutotype;
}
changeAutoTypeTitleFilter(e) {
const autoTypeTitleFilterEnabled = e.target.checked || false;
AppSettingsModel.autoTypeTitleFilterEnabled = autoTypeTitleFilterEnabled;
}
changeFieldLabelDblClickAutoType(e) {
const fieldLabelDblClickAutoType = e.target.checked || false;
AppSettingsModel.fieldLabelDblClickAutoType = fieldLabelDblClickAutoType;

View File

@ -334,8 +334,11 @@
}
}
&__donate-btn {
$donate-border-color: #89abed;
background: #fff;
border: 2px solid #89abed;
border: 2px solid $donate-border-color;
border-bottom: 2px solid $donate-border-color;
border-radius: 2.8rem;
text-align: center;
display: inline-block;
@ -345,6 +348,10 @@
height: 2.8rem;
margin-bottom: $base-padding-v * 2;
&:hover {
border-bottom: 2px solid $donate-border-color;
}
&-top,
&-bottom {
pointer-events: none;

View File

@ -82,6 +82,7 @@ $fa-var-file-image: next-fa-glyph();
$fa-var-file-video: next-fa-glyph();
$fa-var-file-audio: next-fa-glyph();
$fa-var-onedrive: next-fa-glyph();
$fa-var-user-friends: next-fa-glyph();
$fa-var-question: next-fa-glyph();
$fa-var-sign-out-alt: next-fa-glyph();
$fa-var-sync-alt: next-fa-glyph();

View File

@ -3,7 +3,7 @@
<p>{{#res 'setAboutFirst'}}<a href="https://antelle.net" target="_blank">Antelle</a>{{/res~}}&nbsp;
{{~#res 'setAboutSecond'}}<a href="{{licenseLink}}" target="_blank">MIT</a>{{/res}}
{{#res 'setAboutSource'}}<a href="{{repoLink}}" target="_blank">GitHub <i class="fa fa-github-alt bottom"></i></a>{{/res}}</p>
<a href="{{donationLink}}" target="_blank" class="settings__donate-btn no-border">
<a href="{{donationLink}}" target="_blank" class="settings__donate-btn">
<span class="settings__donate-btn-top">Become a</span><span class="settings__donate-btn-bottom">Backer</span>
</a>
<p>{{res 'setAboutBuilt'}}:</p>

View File

@ -24,33 +24,42 @@
<td>{{perBrowser.browserName}}</td>
{{#each perBrowser.extensions as |setting|}}
<td>
{{#if setting.supported}}
<input type="checkbox"
class="check-enable-for-browser"
{{#if setting.enabled}}checked{{/if}}
id="check-enable-{{setting.alias}}-for-{{perBrowser.browser}}"
data-browser="{{perBrowser.browser}}"
data-extension="{{setting.alias}}" />
<label for="check-enable-{{setting.alias}}-for-{{perBrowser.browser}}"></label>
{{#if setting.manualUrl}}
<a href="{{setting.manualUrl}}" target="_blank" rel="noreferrer"
class="settings__browser-extension-link icon-link"
title="{{res 'setBrowserExtensionHelp'}}"
>
<i class="fa fa-info-circle"></i>
</a>
{{else}}
<i class="fa fa-times muted-color" title="{{res 'setBrowserExtensionNotSupported'}}"></i>
{{/if}}
{{#if setting.enabled}}
{{#if setting.helpUrl}}
<a href="{{setting.helpUrl}}" target="_blank" rel="noreferrer"
class="settings__browser-extension-link icon-link"
title="{{res 'setBrowserExtensionHelp'}}"
>
<i class="fa fa-info-circle"></i>
</a>
{{#if setting.supported}}
<input type="checkbox"
class="check-enable-for-browser"
{{#if setting.enabled}}checked{{/if}}
id="check-enable-{{setting.alias}}-for-{{perBrowser.browser}}"
data-browser="{{perBrowser.browser}}"
data-extension="{{setting.alias}}" />
<label for="check-enable-{{setting.alias}}-for-{{perBrowser.browser}}"></label>
{{else}}
<i class="fa fa-times muted-color" title="{{res 'setBrowserExtensionNotSupported'}}"></i>
{{/if}}
{{#if setting.installUrl}}
<a href="{{setting.installUrl}}" target="_blank" rel="noreferrer"
class="settings__browser-extension-link icon-link"
title="{{res 'setBrowserExtensionInstall'}}"
>
<i class="fa fa-download"></i>
</a>
{{#if setting.enabled}}
{{#if setting.helpUrl}}
<a href="{{setting.helpUrl}}" target="_blank" rel="noreferrer"
class="settings__browser-extension-link icon-link"
title="{{res 'setBrowserExtensionHelp'}}"
>
<i class="fa fa-info-circle"></i>
</a>
{{/if}}
{{#if setting.installUrl}}
<a href="{{setting.installUrl}}" target="_blank" rel="noreferrer"
class="settings__browser-extension-link icon-link"
title="{{res 'setBrowserExtensionInstall'}}"
>
<i class="fa fa-download"></i>
</a>
{{/if}}
{{/if}}
{{/if}}
</td>

View File

@ -169,6 +169,11 @@
id="settings__general-direct-autotype" {{#if directAutotype}}checked{{/if}} />
<label for="settings__general-direct-autotype">{{res 'setGenDirectAutotype'}}</label>
</div>
<div>
<input type="checkbox" class="settings__input input-base settings__general-autotype-title-filter"
id="settings__general-autotype-title-filter" {{#if autoTypeTitleFilterEnabled}}checked{{/if}} />
<label for="settings__general-autotype-title-filter">{{res 'setGenAutoTypeTitleFilterEnabled'}}</label>
</div>
<div>
<input type="checkbox" class="settings__input input-base settings__general-field-label-dblclick-autotype"
id="settings__general-field-label-dblclick-autotype" {{#if fieldLabelDblClickAutoType}}checked{{/if}} />

View File

@ -3,21 +3,19 @@ module.exports = function (grunt) {
const done = this.async();
const opt = this.options();
const file = this.files[0].src[0];
const path = require('path');
const puppeteer = require('puppeteer');
(async function () {
grunt.log.writeln('Running tests...');
const fullPath = 'file://' + path.resolve(file);
const browser = await puppeteer.launch({
headless: opt.headless
headless: opt.headless,
executablePath: process.env.CHROME_BIN || null,
args: ['--disable-dev-shm-usage']
});
grunt.log.writeln('puppeteer launched...');
const page = await browser.newPage();
await page.goto(fullPath);
async function check() {
const result = await page.evaluate(() => {
const { output, done } = window;

View File

@ -202,7 +202,9 @@ main.minimizeApp = function (menuItemLabels) {
if (!appIcon) {
const image = electron.nativeImage.createFromPath(path.join(__dirname, 'img', imagePath));
appIcon = new electron.Tray(image);
appIcon.on('click', restoreMainWindow);
if (process.platform !== 'darwin') {
appIcon.on('click', restoreMainWindow);
}
const contextMenu = electron.Menu.buildFromTemplate([
{ label: menuItemLabels.restore, click: restoreMainWindow },
{ label: menuItemLabels.quit, click: closeMainWindow }
@ -376,6 +378,9 @@ function restoreMainWindow() {
}
function showAndFocusMainWindow() {
if (appIcon) {
restoreMainWindow();
}
if (mainWindowMaximized) {
mainWindow.maximize();
} else {

View File

@ -1,6 +1,6 @@
{
"name": "KeeWeb",
"version": "1.18.3",
"version": "1.18.7",
"description": "Free cross-platform password manager compatible with KeePass",
"main": "main.js",
"homepage": "https://keeweb.info",

View File

@ -306,24 +306,29 @@ async function processFirstMessageFromSocket(socket, message) {
let parentProcessInfo;
try {
parentProcessInfo = await getProcessInfo(message.ppid);
} catch (e) {
logger.error(`Cannot get info for PID ${message.ppid}: ${e}`);
socket.destroy();
return;
}
if (process.platform === 'win32' && parentProcessInfo.appName === 'cmd') {
try {
parentProcessInfo = await getProcessInfo(parentProcessInfo.ppid);
parentProcessInfo = await getProcessInfo(message.ppid);
} catch (e) {
logger.error(
`Cannot get info for PID ${parentProcessInfo.ppid}: ${e}, assuming cmd is the launcher`
);
logger.error(`Cannot get info for PID ${message.ppid}: ${e}`);
throw e;
}
if (process.platform === 'win32' && parentProcessInfo.appName === 'cmd') {
try {
parentProcessInfo = await getProcessInfo(parentProcessInfo.ppid);
} catch (e) {
logger.error(
`Cannot get info for PID ${parentProcessInfo.ppid}: ${e}, assuming cmd is the launcher`
);
}
}
} catch (e) {
logger.warn('Cannot get process info, assuming the connection is not identified');
}
appName = AppNames[parentProcessInfo.appName] ?? parentProcessInfo.appName;
appName = parentProcessInfo
? AppNames[parentProcessInfo.appName] ?? parentProcessInfo.appName
: 'Unidentified browser';
appName = appName[0].toUpperCase() + appName.substr(1);
}
@ -393,7 +398,11 @@ function sendMessageToSocket(socket, message) {
const lengthBytes = Buffer.from(lengthBuf);
const data = Buffer.concat([lengthBytes, responseData]);
socket.write(data);
try {
socket.write(data);
} catch (e) {
logger.error(`Error writing to socket ${state.socketId}`, e);
}
}
function sendToRenderer(event, socketId, data) {

View File

@ -39,7 +39,7 @@ function installDarwinUpdate(updateFilePath) {
`--app=${appPath}`
]);
electron.app.exit(0);
electron.app.quit();
}
function installWin32Update(updateFilePath) {
@ -52,7 +52,7 @@ function installWin32Update(updateFilePath) {
const ps = spawnDetached('cmd');
ps.stdin.end(`${updateCommand}\nexit\n`, 'utf8', () => {
electron.app.exit(0);
electron.app.quit();
});
}

1070
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "keeweb",
"version": "1.18.3",
"version": "1.18.7",
"description": "Free cross-platform password manager compatible with KeePass",
"main": "Gruntfile.js",
"private": true,
@ -16,7 +16,7 @@
"@babel/plugin-proposal-optional-chaining": "^7.13.12",
"@babel/preset-env": "^7.14.1",
"@fortawesome/fontawesome-free": "^5.15.3",
"@keeweb/keeweb-native-messaging-host": "https://github.com/keeweb/keeweb-connect/releases/download/0.3.3/keeweb-native-messaging-host.tgz",
"@keeweb/keeweb-native-messaging-host": "https://github.com/keeweb/keeweb-connect/releases/download/0.3.5/keeweb-native-messaging-host.tgz",
"@keeweb/keeweb-native-modules": "https://github.com/keeweb/keeweb-native-modules/releases/download/0.11.6/keeweb-native-modules.tgz",
"adm-zip": "^0.5.5",
"argon2-browser": "1.15.4",
@ -32,7 +32,7 @@
"cross-env": "^7.0.3",
"css-loader": "^5.2.4",
"dompurify": "^2.2.8",
"electron": "^12.0.7",
"electron": "^13.6.9",
"electron-builder": "^22.11.1",
"electron-evil-feature-patcher": "^1.2.1",
"electron-notarize": "^1.0.0",
@ -69,7 +69,7 @@
"jquery": "3.6.0",
"json-loader": "^0.5.7",
"jsqrcode": "github:antelle/jsqrcode#0.1.3",
"kdbxweb": "^2.0.1",
"kdbxweb": "^2.0.4",
"load-grunt-tasks": "5.1.0",
"lodash": "^4.17.21",
"marked": "^2.0.3",
@ -110,20 +110,23 @@
},
"scripts": {
"start": "grunt",
"lint": "grunt eslint",
"test": "grunt test",
"build-beta": "grunt --beta && cp dist/index.html ../keeweb-beta/index.html && cd ../keeweb-beta && git add index.html && git commit -a -m 'beta' && git push origin master",
"electron": "cross-env KEEWEB_IS_PORTABLE=0 ELECTRON_DISABLE_SECURITY_WARNINGS=1 KEEWEB_EMULATE_HARDWARE_ENCRYPTION=persistent KEEWEB_HTML_PATH=http://localhost:8085 electron desktop --no-sandbox",
"dev": "grunt dev",
"dev-legacy": "cross-env NODE_OPTIONS='--openssl-legacy-provider' grunt dev",
"dev-desktop-macos": "grunt dev-desktop-darwin --skip-sign",
"dev-desktop-macos-signed": "grunt dev-desktop-darwin-signed",
"dev-desktop-windows": "grunt dev-desktop-win32 --skip-sign",
"dev-desktop-linux": "grunt dev-desktop-linux --skip-sign",
"babel-helpers": "babel-external-helpers -l 'slicedToArray,toConsumableArray,defineProperty,typeof' -t global > app/lib/babel-helpers.js"
"babel-helpers": "babel-external-helpers -l 'slicedToArray,toConsumableArray,defineProperty,typeof' -t global > app/lib/babel-helpers.js",
"set-legacy": "export NODE_OPTIONS=--openssl-legacy-provider"
},
"author": {
"name": "Antelle",
"email": "antelle.net@gmail.com",
"url": "http://antelle.net"
"url": "https://antelle.net"
},
"engines": {
"node": ">=10.0"
@ -140,4 +143,4 @@
"last 1 ChromeAndroid version",
"Electron > 6.0.1"
]
}
}

View File

@ -1,5 +1,28 @@
Release notes
-------------
##### v1.18.7 (2021-07-18)
`+` added Microsoft Teams storage
`+` added a possibility to override tenant in Microsoft OneDrive
`!` disabled automatic installation of KeePassXC-Browser extension
`+` added an option to diagnose YubiKey code listing issues
`-` fix #1845: fixed a visible crash on socket write error
##### v1.18.6 (2021-05-19)
`-` fix #1824: saving KDBX3 files with compression disabled
`-` fix #1818: extension connection error if browser cannot be identified
`-` fix #1820: minimize on close on macOS
##### v1.18.5 (2021-05-14)
`-` fix #1816: old Chromium support, such as Android Edge
`-` fix #1817: crash on files with large attachments as KDBX3
##### v1.18.4 (2021-05-12)
`+` #1814: option to disable auto-type title filter by default
`-` #1808: restore KeeWeb from system tray on extension request
`-` fix #1810: extension connection on old macOS (10.11)
`-` fix #1813: custom icon selection
`-` fix #1811: app doesn't quit during update
##### v1.18.3 (2021-05-09)
`-` fix #1804: filling OTP in browser extensions
`*` fix #1805: auto-unchecking auto-type filters if nothing found