Merge branch 'main' into feature/email-notifications-failed-login

This commit is contained in:
Bruno Bernardino 2022-08-01 08:47:52 +01:00
commit 052477b6c9
No known key found for this signature in database
GPG Key ID: D1B0A69ADD114ECE
30 changed files with 287 additions and 130 deletions

View File

@ -1,6 +1,6 @@
# Padloc
[![](https://github.com/padloc/padloc/workflows/Run%20Tests/badge.svg?branch=v4)](https://github.com/padloc/padloc/actions?workflow=Run+Tests)
[![](https://github.com/padloc/padloc/workflows/Run%20Tests/badge.svg?branch=main)](https://github.com/padloc/padloc/actions?workflow=Run+Tests)
Simple, secure password and data management for individuals and teams.

View File

@ -1,7 +0,0 @@
<p>The following error occurred at {{ time }}:</p>
<p>
Code: {{ code }} <br />
Message: {{ message }} <br />
Event ID: {{ eventId }}`
</p>

View File

@ -1,5 +0,0 @@
The following error occurred at {{ time }}:
Code: {{ code }}
Message: {{ message }}
Event ID: {{ eventId }}`

3
assets/email/plain.html Normal file
View File

@ -0,0 +1,3 @@
<pre>
{{ message }}
</pre>

1
assets/email/plain.txt Normal file
View File

@ -0,0 +1 @@
{{ message }}

View File

@ -1,39 +1,54 @@
<a href="#">
<button class="fill-horizontally">
<div class="horizontal spacing layout">
<i class="fa-user block"></i>
<div class="stretch">FAQs</div>
<ul class="plain box">
<li class="list-item">
<a href="https://padloc.app/" target="_blank" class="plain double-padded horizontal spacing center-aligning layout">
<i class="fa-globe block"></i>
<div class="stretch">Website</div>
<i class="subtle block fa-external-link"></i>
</div>
</button>
</a>
</a>
</li>
<li class="list-item">
<a href="https://padloc.app/blog/" target="_blank" class="plain double-padded horizontal spacing center-aligning layout">
<i class="fa-megaphone block"></i>
<div class="stretch">Blog</div>
<i class="subtle block fa-external-link"></i>
</a>
</li>
<li class="list-item">
<a href="https://padloc.app/tos/" target="_blank" class="plain double-padded horizontal spacing center-aligning layout">
<i class="fa-file-lines block"></i>
<div class="stretch">Terms of Service</div>
<i class="subtle block fa-external-link"></i>
</a>
</li>
<li class="list-item">
<a href="https://docs.padloc.app/privacy/" target="_blank" class="plain double-padded horizontal spacing center-aligning layout">
<i class="fa-blinds block"></i>
<div class="stretch">Privacy Policy</div>
<i class="subtle block fa-external-link"></i>
</a>
</li>
</ul>
<a href="#">
<button class="fill-horizontally">
<div class="horizontal spacing layout">
<i class="fa-lock lock block"></i>
<div class="stretch">Support Ticket</div>
<ul class="plain box">
<li class="list-item">
<a href="mailto:support@padloc.app" target="_blank" class="plain double-padded horizontal spacing center-aligning layout">
<i class="fa-envelope block"></i>
<div class="stretch">Contact Support</div>
<i class="subtle block fa-external-link"></i>
</div>
</button>
</a>
<a href="#">
<button class="fill-horizontally">
<div class="horizontal spacing layout">
<i class="fa-comments-alt block"></i>
<div class="stretch">Live Chat</div>
</a>
</li>
<li class="list-item">
<a href="https://docs.padloc.app/manual/" target="_blank" class="plain double-padded horizontal spacing center-aligning layout">
<i class="fa-book block"></i>
<div class="stretch">User Manual</div>
<i class="subtle block fa-external-link"></i>
</div>
</button>
</a>
<a href="#">
<button class="fill-horizontally">
<div class="horizontal spacing layout">
<i class="fa-tools block"></i>
<div class="stretch">Support Center</div>
</a>
</li>
<li class="list-item">
<a href="https://docs.padloc.app/questions/" target="_blank" class="plain double-padded horizontal spacing center-aligning layout">
<i class="fa-comments-question block"></i>
<div class="stretch">Frequently Asked Questions</div>
<i class="subtle block fa-external-link"></i>
</div>
</button>
</a>
</a>
</li>
</ul>

View File

@ -184,6 +184,22 @@
# PL_LOGGING_MONGODB_MAX_SIZE=""
# PL_LOGGING_MONGODB_MAX_DOCUMENTS=""
# -----------------------------------------------------------------------------
# POSTGRES
#
# Use postgresql as log storage: https://www.npmjs.com/package/pg
# -----------------------------------------------------------------------------
# PL_LOGGING_BACKEND=postgres
# PL_LOGGING_POSTGRES_HOST=localhost
# PL_LOGGING_POSTGRES_PORT=5432
# PL_LOGGING_POSTGRES_DATABASE=padloc
# PL_LOGGING_POSTGRES_USER=padloc
# PL_LOGGING_POSTGRES_PASSWORD=""
# PL_LOGGING_POSTGRES_TLS=false
# PL_LOGGING_POSTGRES_TLS_CAFILE=""
# PL_LOGGING_POSTGRES_TLS_REJECT_UNAUTHORIZED=true
# -----------------------------------------------------------------------------
# MIXPANEL
#

View File

@ -3,6 +3,7 @@ import { html, css, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators.js";
import { Dialog } from "./dialog";
import "./button";
import "./icon";
const defaultButtonLabel = $l("OK");
@ -20,6 +21,7 @@ export interface AlertOptions {
maxWidth?: string;
width?: string;
hideOnDocumentVisibilityChange?: boolean;
zIndex?: number;
}
@customElement("pl-alert-dialog")
@ -119,6 +121,7 @@ export class AlertDialog extends Dialog<AlertOptions, number> {
maxWidth,
width,
hideOnDocumentVisibilityChange = false,
zIndex = 10,
}: AlertOptions = {}): Promise<number> {
this.message = message;
this.dialogTitle = title;
@ -137,6 +140,7 @@ export class AlertDialog extends Dialog<AlertOptions, number> {
await this.updateComplete;
this.style.zIndex = zIndex.toString();
this._inner.style.setProperty("--pl-dialog-max-width", maxWidth || "inherit");
this._inner.style.setProperty("--pl-dialog-width", width || "inherit");

View File

@ -95,6 +95,7 @@ export class WebPlatform extends StubPlatform implements Platform {
browser.name && browser.name !== "Electron"
? $l("{0} on {1}", browser.name, platform)
: $l("{0} Device", platform),
runtime: "web",
});
}

View File

@ -39,6 +39,7 @@ async function alertMessage(message: string | RichContent, action?: { label: str
type: !action ? "choice" : "info",
title,
hideOnDocumentVisibilityChange: true,
zIndex: 20,
}
);
if (action && choice === 0) {

View File

@ -65,7 +65,8 @@ export const content = css`
list-style: square;
}
ul.plain {
ul.plain,
ul.unstyled {
list-style: none;
padding: 0;
}
@ -96,10 +97,8 @@ export const content = css`
color: var(--button-primary-color, var(--button-color));
}
${click("button")};
${hover("button")};
a.plain {
a.plain,
a.unstyled {
text-decoration: none !important;
}
@ -128,4 +127,7 @@ export const content = css`
margin-bottom: 0.5em;
overflow-x: auto;
}
${click("button")};
${hover("button")};
`;

View File

@ -8,7 +8,6 @@
"name": "@padloc/cordova",
"version": "4.0.0",
"dependencies": {
"cordova-android": "10.1.0",
"cordova-clipboard": "1.3.0",
"cordova-ios": "6.2.0",
"cordova-plugin-add-swift-support": "2.0.2",
@ -32,6 +31,7 @@
"@types/cordova-plugin-qrscanner": "1.0.31",
"clean-webpack-plugin": "3.0.0",
"cordova": "11.0.0",
"cordova-android": "^10.1.2",
"css-loader": "5.2.6",
"dotenv": "10.0.0",
"file-loader": "6.2.0",
@ -756,6 +756,7 @@
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/android-versions/-/android-versions-1.7.0.tgz",
"integrity": "sha512-TCy4b8Dk8YS6A23ZPfhSKqK66JHFq0D8avGYiwvYpjno6HrrcI0DRgHx9+jtkvWYmrsE2vQWgbHJhvGGhhOb0g==",
"dev": true,
"dependencies": {
"semver": "^5.7.1"
}
@ -764,6 +765,7 @@
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
"dev": true,
"bin": {
"semver": "bin/semver"
}
@ -1852,9 +1854,10 @@
}
},
"node_modules/cordova-android": {
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/cordova-android/-/cordova-android-10.1.0.tgz",
"integrity": "sha512-nkVh8VMDWoDmvWaQav/sF3Q7N9ZpRRNBJduDhjCOMwYUF/6q2526i/Jb/wJLdXmetBNh+THkGolcTBi9i+1UzA==",
"version": "10.1.2",
"resolved": "https://registry.npmjs.org/cordova-android/-/cordova-android-10.1.2.tgz",
"integrity": "sha512-F28+NvgKO4ZhKFkqctCOh62mhVoNyUuRQh/F/nqp+Sti4ODv2rUa6UeW18khhdYTjlDeihHQsPqxvB7mI6fVYA==",
"dev": true,
"dependencies": {
"android-versions": "^1.7.0",
"cordova-common": "^4.0.2",
@ -1873,9 +1876,10 @@
}
},
"node_modules/cordova-android/node_modules/fs-extra": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.0.tgz",
"integrity": "sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ==",
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
"integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==",
"dev": true,
"dependencies": {
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
@ -2963,6 +2967,7 @@
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
"integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==",
"dev": true,
"dependencies": {
"cross-spawn": "^7.0.3",
"get-stream": "^6.0.0",
@ -3360,6 +3365,7 @@
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
"integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
"dev": true,
"engines": {
"node": ">=10"
},
@ -3757,6 +3763,7 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
"integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==",
"dev": true,
"engines": {
"node": ">=10.17.0"
}
@ -4312,6 +4319,7 @@
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
"integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
"dev": true,
"engines": {
"node": ">=8"
}
@ -4341,6 +4349,7 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
"integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
"dev": true,
"engines": {
"node": ">=8"
},
@ -4752,7 +4761,8 @@
"node_modules/merge-stream": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
"integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="
"integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
"dev": true
},
"node_modules/merge2": {
"version": "1.4.1",
@ -4820,6 +4830,7 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
"integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
"dev": true,
"engines": {
"node": ">=6"
}
@ -5225,6 +5236,7 @@
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
"integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==",
"dev": true,
"dependencies": {
"path-key": "^3.0.0"
},
@ -5321,6 +5333,7 @@
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
"integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
"dev": true,
"dependencies": {
"mimic-fn": "^2.1.0"
},
@ -5908,7 +5921,8 @@
"node_modules/properties-parser": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/properties-parser/-/properties-parser-0.3.1.tgz",
"integrity": "sha1-ExbpU5/7/ZOEXjabIRAiq9R4dxo=",
"integrity": "sha512-AkSQxQAviJ89x4FIxOyHGfO3uund0gvYo7lfD0E+Gp7gFQKrTNgtoYQklu8EhrfHVZUzTwKGZx2r/KDSfnljcA==",
"dev": true,
"dependencies": {
"string.prototype.codepointat": "^0.2.0"
},
@ -6713,7 +6727,8 @@
"node_modules/signal-exit": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz",
"integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA=="
"integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==",
"dev": true
},
"node_modules/simctl": {
"version": "2.0.3",
@ -7041,7 +7056,8 @@
"node_modules/string.prototype.codepointat": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/string.prototype.codepointat/-/string.prototype.codepointat-0.2.1.tgz",
"integrity": "sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg=="
"integrity": "sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg==",
"dev": true
},
"node_modules/stringify-package": {
"version": "1.0.1",
@ -7073,6 +7089,7 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
"integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==",
"dev": true,
"engines": {
"node": ">=6"
}
@ -7594,6 +7611,7 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz",
"integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==",
"dev": true,
"engines": {
"node": ">=8"
}
@ -8849,6 +8867,7 @@
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/android-versions/-/android-versions-1.7.0.tgz",
"integrity": "sha512-TCy4b8Dk8YS6A23ZPfhSKqK66JHFq0D8avGYiwvYpjno6HrrcI0DRgHx9+jtkvWYmrsE2vQWgbHJhvGGhhOb0g==",
"dev": true,
"requires": {
"semver": "^5.7.1"
},
@ -8856,7 +8875,8 @@
"semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
"dev": true
}
}
},
@ -9697,9 +9717,10 @@
}
},
"cordova-android": {
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/cordova-android/-/cordova-android-10.1.0.tgz",
"integrity": "sha512-nkVh8VMDWoDmvWaQav/sF3Q7N9ZpRRNBJduDhjCOMwYUF/6q2526i/Jb/wJLdXmetBNh+THkGolcTBi9i+1UzA==",
"version": "10.1.2",
"resolved": "https://registry.npmjs.org/cordova-android/-/cordova-android-10.1.2.tgz",
"integrity": "sha512-F28+NvgKO4ZhKFkqctCOh62mhVoNyUuRQh/F/nqp+Sti4ODv2rUa6UeW18khhdYTjlDeihHQsPqxvB7mI6fVYA==",
"dev": true,
"requires": {
"android-versions": "^1.7.0",
"cordova-common": "^4.0.2",
@ -9715,9 +9736,10 @@
},
"dependencies": {
"fs-extra": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.0.tgz",
"integrity": "sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ==",
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
"integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==",
"dev": true,
"requires": {
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
@ -10525,6 +10547,7 @@
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
"integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==",
"dev": true,
"requires": {
"cross-spawn": "^7.0.3",
"get-stream": "^6.0.0",
@ -10837,7 +10860,8 @@
"get-stream": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
"integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg=="
"integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
"dev": true
},
"getpass": {
"version": "0.1.7",
@ -11132,7 +11156,8 @@
"human-signals": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
"integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw=="
"integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==",
"dev": true
},
"humanize-ms": {
"version": "1.2.1",
@ -11541,7 +11566,8 @@
"is-path-inside": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
"integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ=="
"integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
"dev": true
},
"is-plain-object": {
"version": "2.0.4",
@ -11563,7 +11589,8 @@
"is-stream": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
"integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="
"integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
"dev": true
},
"is-typedarray": {
"version": "1.0.0",
@ -11892,7 +11919,8 @@
"merge-stream": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
"integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="
"integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
"dev": true
},
"merge2": {
"version": "1.4.1",
@ -11938,7 +11966,8 @@
"mimic-fn": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
"integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="
"integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
"dev": true
},
"mimic-response": {
"version": "1.0.1",
@ -12254,6 +12283,7 @@
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
"integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==",
"dev": true,
"requires": {
"path-key": "^3.0.0"
}
@ -12329,6 +12359,7 @@
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
"integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
"dev": true,
"requires": {
"mimic-fn": "^2.1.0"
}
@ -12760,7 +12791,8 @@
"properties-parser": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/properties-parser/-/properties-parser-0.3.1.tgz",
"integrity": "sha1-ExbpU5/7/ZOEXjabIRAiq9R4dxo=",
"integrity": "sha512-AkSQxQAviJ89x4FIxOyHGfO3uund0gvYo7lfD0E+Gp7gFQKrTNgtoYQklu8EhrfHVZUzTwKGZx2r/KDSfnljcA==",
"dev": true,
"requires": {
"string.prototype.codepointat": "^0.2.0"
}
@ -13374,7 +13406,8 @@
"signal-exit": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz",
"integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA=="
"integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==",
"dev": true
},
"simctl": {
"version": "2.0.3",
@ -13617,7 +13650,8 @@
"string.prototype.codepointat": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/string.prototype.codepointat/-/string.prototype.codepointat-0.2.1.tgz",
"integrity": "sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg=="
"integrity": "sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg==",
"dev": true
},
"stringify-package": {
"version": "1.0.1",
@ -13642,7 +13676,8 @@
"strip-final-newline": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
"integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA=="
"integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==",
"dev": true
},
"strip-json-comments": {
"version": "2.0.1",
@ -14006,7 +14041,8 @@
"untildify": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz",
"integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw=="
"integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==",
"dev": true
},
"update-notifier": {
"version": "5.1.0",

View File

@ -40,7 +40,6 @@
"dependencies": {
"@padloc/app": "4.0.0",
"@padloc/core": "4.0.0",
"cordova-android": "10.1.0",
"cordova-clipboard": "1.3.0",
"cordova-ios": "6.2.0",
"cordova-plugin-add-swift-support": "2.0.2",
@ -64,6 +63,7 @@
"@types/cordova-plugin-qrscanner": "1.0.31",
"clean-webpack-plugin": "3.0.0",
"cordova": "11.0.0",
"cordova-android": "10.1.2",
"css-loader": "5.2.6",
"dotenv": "10.0.0",
"file-loader": "6.2.0",

View File

@ -49,6 +49,7 @@ export class CordovaPlatform extends WebPlatform implements Platform {
platform,
osVersion,
description: appleDeviceNames[model] || model,
runtime: "cordova",
});
}

View File

@ -231,6 +231,9 @@ export class Auth extends Serializable implements Storable {
@AsSerializable(PBES2Container)
legacyData?: PBES2Container;
/** Completely disables mfa for a given account. Only use for testing! */
disableMFA = false;
constructor(public email: string = "") {
super();
}

View File

@ -99,8 +99,10 @@ export class Err extends Error {
}
toString() {
return `Time: ${this.time.toISOString()}\nError Code: ${this.code}:\nError Message: ${
this.message
}\nStack Trace:\n${this.originalError ? this.originalError.stack : this.stack}`;
return `Time: ${this.time.toISOString()}
Error Code: ${this.code}
Error Message: ${this.message}
Stack Trace:\n${this.originalError ? this.originalError.stack : this.stack}
`;
}
}

View File

@ -69,8 +69,8 @@ export class FailedLoginAttemptMessage extends Message<{ location: string }> {
}
}
export class ErrorMessage extends Message<{ code: string; message: string; time: string; eventId: string }> {
template = "error";
export class PlainMessage extends Message<{ message: string }> {
template = "plain";
get title() {
return "Padloc Error Notification";

View File

@ -47,6 +47,8 @@ export class DeviceInfo extends Serializable {
description: string = $l("Unknown Device");
runtime: string = "";
constructor(props?: Partial<DeviceInfo>) {
super();
props && Object.assign(this, props);

View File

@ -3,6 +3,7 @@ import { Config, ConfigParam } from "./config";
import { AsSerializable, Serializable } from "./encoding";
import { Err, ErrorCode } from "./error";
import { Org, OrgID, OrgInfo } from "./org";
import { Session } from "./session";
import { Storable, Storage } from "./storage";
import { getIdFromEmail } from "./util";
@ -215,7 +216,7 @@ export class Provisioning extends Serializable {
}
export interface Provisioner {
getProvisioning(params: { email: string; accountId?: AccountID }): Promise<Provisioning>;
getProvisioning(params: { email: string; accountId?: AccountID }, session?: Session): Promise<Provisioning>;
accountDeleted(params: { email: string; accountId?: AccountID }): Promise<void>;
orgDeleted(params: OrgInfo): Promise<void>;
orgOwnerChanged(

View File

@ -44,7 +44,7 @@ import { Org, OrgID, OrgMember, OrgMemberStatus, OrgRole, ScimSettings } from ".
import { Invite } from "./invite";
import {
ConfirmMembershipInviteMessage,
ErrorMessage,
PlainMessage,
JoinOrgInviteAcceptedMessage,
JoinOrgInviteCompletedMessage,
JoinOrgInviteMessage,
@ -200,13 +200,11 @@ export class Controller extends API {
// Get account associated with this session
const account = await this.storage.get(Account, session.account);
const auth = await this._getAuth(account.email);
const provisioning = await this.provisioner.getProvisioning(auth);
// Store account and session on context
ctx.session = session;
ctx.account = account;
ctx.auth = auth;
ctx.provisioning = provisioning;
ctx.location = req.location;
// Update session info
@ -222,6 +220,8 @@ export class Controller extends API {
auth.sessions.push(session.info);
}
ctx.provisioning = await this.provisioner.getProvisioning(auth, session);
await Promise.all([this.storage.save(session), this.storage.save(account), this.storage.save(auth)]);
}
@ -356,7 +356,8 @@ export class Controller extends API {
auth.authRequests.push(request);
const deviceTrusted =
this.context.device && auth.trustedDevices.some(({ id }) => id === this.context.device!.id);
auth.disableMFA ||
(this.context.device && auth.trustedDevices.some(({ id }) => id === this.context.device!.id));
const response = new StartAuthRequestResponse({
id: request.id,
@ -2121,8 +2122,6 @@ export class Server {
}
private async _handleError(error: Error, req: Request, res: Response, context: Context) {
console.error(error);
const e =
error instanceof Err
? error
@ -2139,6 +2138,8 @@ export class Server {
};
if (e.report) {
console.error(error);
const evt = this.log("error", context, {
error: e.toRaw(),
request: {
@ -2151,11 +2152,12 @@ export class Server {
try {
await this.messenger.send(
this.config.reportErrors,
new ErrorMessage({
time: e.time.toISOString(),
code: e.code,
message: e.message,
eventId: evt.id,
new PlainMessage({
message: `The following error occured at ${e.time.toISOString()}:\n\nEndpoint: ${
req.method
}\nDevice Info:\n${
req.device && JSON.stringify(req.device?.toRaw(), null, 4)
}\n${e.toString()}${evt?.id ? `Event ID: ${evt.id}` : ""}`,
})
);
} catch (e) {}

View File

@ -21,6 +21,18 @@ export async function uuid(): Promise<string> {
].join("-");
}
/**
* Generates a random UUID v4
* NOT CRYPTOGRAPHICALLY SAFE!
*/
export function unsafeUUID(): string {
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
var r = (Math.random() * 16) | 0,
v = c == "x" ? r : (r & 0x3) | 0x8;
return v.toString(16);
});
}
/** Caracters, by category */
export const chars = {
numbers: "0123456789",

View File

@ -62,6 +62,7 @@ async function main() {
snap: {
confinement: "strict",
plugs: ["desktop", "home", "browser-support", "network", "opengl", "x11", "wayland", "unity7"],
publish: ["github"],
},
afterSign: "scripts/notarize.js",
};

View File

@ -1,4 +1,10 @@
import { Platform } from "@padloc/core/src/platform";
import { WebPlatform } from "@padloc/app/src/lib/platform";
export class ElectronPlatform extends WebPlatform implements Platform {}
export class ElectronPlatform extends WebPlatform implements Platform {
async getDeviceInfo() {
const device = await super.getDeviceInfo();
device.runtime = "electron";
return device;
}
}

View File

@ -12,6 +12,7 @@ export class ExtensionPlatform extends WebPlatform {
async getDeviceInfo() {
const info = await super.getDeviceInfo();
info.description = `${info.browser} extension on ${info.platform}`;
info.runtime = "extension";
return info;
}
}

View File

@ -82,7 +82,7 @@ export class LoggingConfig extends Config {
}
@ConfigParam()
backend: "void" | "mongodb" | "mixpanel" = "void";
backend: "void" | "mongodb" | "postgres" | "mixpanel" = "void";
@ConfigParam()
secondaryBackend?: "mongodb" | "mixpanel";
@ -90,6 +90,9 @@ export class LoggingConfig extends Config {
@ConfigParam(MongoDBStorageConfig)
mongodb?: MongoDBStorageConfig;
@ConfigParam(PostgresConfig)
postgres?: PostgresConfig;
@ConfigParam(MixpanelConfig)
mixpanel?: MixpanelConfig;
}

View File

@ -11,7 +11,7 @@ import { AuthServer, AuthType } from "@padloc/core/src/auth";
import { WebAuthnConfig, WebAuthnServer } from "./auth/webauthn";
import { SMTPSender } from "./email/smtp";
import { MongoDBStorage } from "./storage/mongodb";
import { ConsoleMessenger, ErrorMessage } from "@padloc/core/src/messenger";
import { ConsoleMessenger, PlainMessage } from "@padloc/core/src/messenger";
import { FSAttachmentStorage, FSAttachmentStorageConfig } from "./attachments/fs";
import {
AttachmentStorageConfig,
@ -33,11 +33,11 @@ import { resolve, join } from "path";
import { MongoDBLogger } from "./logging/mongodb";
import { MixpanelLogger } from "./logging/mixpanel";
import { PostgresStorage } from "./storage/postgres";
import { ErrorCode } from "@padloc/core/src/error";
import { stripPropertiesRecursive } from "@padloc/core/src/util";
import { DirectoryProvisioner } from "./provisioning/directory";
import { ScimServer, ScimServerConfig } from "./scim";
import { DirectoryProvider, DirectorySync } from "@padloc/core/src/directory";
import { PostgresLogger } from "./logging/postgres";
const rootDir = resolve(__dirname, "../../..");
const assetsDir = resolve(rootDir, process.env.PL_ASSETS_DIR || "assets");
@ -75,7 +75,7 @@ async function initDataStorage(config: DataStorageConfig) {
}
}
async function initLogger({ backend, secondaryBackend, mongodb, mixpanel }: LoggingConfig) {
async function initLogger({ backend, secondaryBackend, mongodb, postgres, mixpanel }: LoggingConfig) {
let primaryLogger: Logger;
switch (backend) {
@ -83,9 +83,15 @@ async function initLogger({ backend, secondaryBackend, mongodb, mixpanel }: Logg
if (!mongodb) {
throw "PL_LOGGING_BACKEND was set to 'mongodb', but no related configuration was found!";
}
const storage = new MongoDBStorage(mongodb);
await storage.init();
primaryLogger = new MongoDBLogger(storage);
const mongoStorage = new MongoDBStorage(mongodb);
await mongoStorage.init();
primaryLogger = new MongoDBLogger(mongoStorage);
break;
case "postgres":
if (!postgres) {
throw "PL_LOGGING_BACKEND was set to 'postgres', but no related configuration was found!";
}
primaryLogger = new PostgresLogger(new PostgresStorage(postgres));
break;
case "void":
primaryLogger = new VoidLogger();
@ -302,11 +308,10 @@ async function init(config: PadlocConfig) {
try {
await emailSender.send(
config.server.reportErrors,
new ErrorMessage({
code: ErrorCode.UNKNOWN_ERROR,
message: `${err.message}\n${err.stack}`,
time: new Date().toISOString(),
eventId: "",
new PlainMessage({
message: `An uncaught exception occured at ${new Date().toISOString()}:\n${err.message}\n${
err.stack
}`,
})
);
} catch (e) {}

View File

@ -0,0 +1,17 @@
import { Logger, LogEvent } from "@padloc/core/src/logging";
import { PostgresStorage } from "../storage/postgres";
export class PostgresLogger implements Logger {
constructor(private _storage: PostgresStorage) {}
log(type: string, data?: any) {
const event = new LogEvent(type, data);
event.id = `${event.time.toISOString()}_${Math.floor(Math.random() * 1e6)}`;
(async () => {
try {
this._storage.save(event);
} catch (e) {}
})();
return event;
}
}

View File

@ -3,4 +3,10 @@ import { NodeCryptoProvider } from "../crypto/node";
export class NodePlatform extends StubPlatform implements Platform {
crypto = new NodeCryptoProvider();
async getDeviceInfo() {
const info = await super.getDeviceInfo();
info.runtime = "node";
return info;
}
}

View File

@ -23,6 +23,7 @@ import { base64ToBytes, bytesToBase64, stringToBytes } from "@padloc/core/src/en
import { HMACKeyParams, HMACParams } from "@padloc/core/src/crypto";
import { URLSearchParams } from "url";
import { Account } from "@padloc/core/src/account";
import { Session } from "@padloc/core/src/session";
export class StripeProvisionerConfig extends BasicProvisionerConfig {
@ConfigParam("string", true)
@ -48,6 +49,9 @@ export class StripeProvisionerConfig extends BasicProvisionerConfig {
@ConfigParam("number")
forceSyncAfter: number = 24 * 60 * 60;
@ConfigParam("string[]")
disableBillingOn = ["ios", "android"];
}
enum Tier {
@ -97,7 +101,7 @@ export class StripeProvisioner extends BasicProvisioner {
[Tier.Premium]: {
order: 1,
name: "Premium",
description: "Power up your password manager!",
description: "Advanced multi-factor authentication, encrypted file storage and more!",
minSeats: undefined,
maxSeats: undefined,
features: [
@ -218,8 +222,8 @@ export class StripeProvisioner extends BasicProvisioner {
}
}
async getProvisioning(opts: { email: string; accountId?: string | undefined }) {
const provisioning = await super.getProvisioning(opts);
async getProvisioning(opts: { email: string; accountId?: string | undefined }, session?: Session) {
let provisioning = await super.getProvisioning(opts);
if (
provisioning.account.accountId &&
(!provisioning.account.metaData?.customer ||
@ -228,7 +232,35 @@ export class StripeProvisioner extends BasicProvisioner {
) {
await this._syncBilling(provisioning);
}
return super.getProvisioning(opts);
provisioning = await super.getProvisioning(opts);
const platform = session?.device?.platform?.toLowerCase() || "";
const runtime = session?.device?.runtime;
if (runtime === "cordova" && this.config.disableBillingOn.includes(platform)) {
provisioning.account.billingPage = undefined;
for (const feature of Object.values(provisioning.account.features)) {
if (feature.disabled) {
feature.message = {
type: "html",
content: html`
<div style="max-width: 20em">
<div class="large text-centering semibold">
<i class="fa-ban"></i> Feature Not Available
</div>
<div class="top-margined">
This feature is not available on this platform yet. Please use the
<a href="https://web.padloc.app">web app</a> instead!
</div>
</div>
`,
};
feature.actionUrl = "https://web.padloc.app";
feature.actionLabel = "Open Web App";
}
}
}
return provisioning;
}
async orgOwnerChanged(
@ -328,21 +360,14 @@ export class StripeProvisioner extends BasicProvisioner {
email,
expand: ["data.subscriptions", "data.tax_ids"],
});
customer = existingCustomers.data.find(
(c) => !c.metadata.org && (!c.metadata.account || c.metadata.account === accountId)
);
customer = existingCustomers.data.find((c) => !c.metadata.account || c.metadata.account === accountId);
}
// Create a new customer
if (!customer || customer.deleted) {
const account = accountId ? await this.storage.get(Account, accountId).catch(() => null) : null;
const testClock = await this._stripe.testHelpers.testClocks.create({
name: `Test Clock for ${email}`,
frozen_time: Math.floor(Date.now() / 1000),
});
customer = await this._stripe.customers.create({
email,
test_clock: testClock.id,
name: account?.name,
metadata: {
account: accountId!,
@ -1464,7 +1489,7 @@ export class StripeProvisioner extends BasicProvisioner {
protected async _handleRequest(httpReq: IncomingMessage, httpRes: ServerResponse) {
const path = new URL(httpReq.url!, "http://localhost").pathname;
if (path === "/stripe_webhooks") {
if (path.endsWith("/stripe_webhooks")) {
if (httpReq.method !== "POST") {
httpRes.statusCode = 405;
httpRes.end();
@ -1474,7 +1499,7 @@ export class StripeProvisioner extends BasicProvisioner {
return this._handleStripeEvent(httpReq, httpRes);
}
if (path === "/sync") {
if (path.endsWith("/sync")) {
if (httpReq.method !== "POST") {
httpRes.statusCode = 405;
httpRes.end();
@ -1484,7 +1509,7 @@ export class StripeProvisioner extends BasicProvisioner {
return this._handleSyncBilling(httpReq, httpRes);
}
if (path === "/portal") {
if (path.endsWith("/portal")) {
if (httpReq.method !== "GET") {
httpRes.statusCode = 405;
httpRes.end();
@ -1494,7 +1519,7 @@ export class StripeProvisioner extends BasicProvisioner {
return this._handlePortalRequest(httpReq, httpRes);
}
if (path == "/callback") {
if (path.endsWith("/callback")) {
if (httpReq.method !== "GET") {
httpRes.statusCode = 405;
httpRes.end();

View File

@ -1,7 +1,10 @@
import { Platform } from "@padloc/core/src/platform";
import { WebPlatform } from "@padloc/app/src/lib/platform";
// import { MemoryStorage } from "@padloc/core/src/storage";
export class TauriPlatform extends WebPlatform implements Platform {
// storage = new MemoryStorage();
async getDeviceInfo() {
const device = await super.getDeviceInfo();
device.runtime = "tauri";
return device;
}
}