Remove failed unlock notification, add new login notification
Also address the other PR requests and tweak the legacy UI tests
This commit is contained in:
parent
052477b6c9
commit
8d0a255ce7
|
@ -0,0 +1,275 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<title>{{ title }}</title>
|
||||
<style>
|
||||
/* -------------------------------------
|
||||
RESPONSIVE AND MOBILE FRIENDLY STYLES
|
||||
------------------------------------- */
|
||||
@media only screen and (max-width: 620px) {
|
||||
table[class="body"] h1 {
|
||||
font-size: 28px !important;
|
||||
margin-bottom: 10px !important;
|
||||
}
|
||||
table[class="body"] p,
|
||||
table[class="body"] ul,
|
||||
table[class="body"] ol,
|
||||
table[class="body"] td,
|
||||
table[class="body"] span,
|
||||
table[class="body"] a {
|
||||
font-size: 16px !important;
|
||||
}
|
||||
table[class="body"] .wrapper,
|
||||
table[class="body"] .article {
|
||||
padding: 10px !important;
|
||||
}
|
||||
table[class="body"] .content {
|
||||
padding: 0 !important;
|
||||
}
|
||||
table[class="body"] .container {
|
||||
padding: 0 !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
table[class="body"] .main {
|
||||
border-left-width: 0 !important;
|
||||
border-radius: 0 !important;
|
||||
border-right-width: 0 !important;
|
||||
}
|
||||
table[class="body"] .btn table {
|
||||
width: 100% !important;
|
||||
}
|
||||
table[class="body"] .btn a {
|
||||
width: 100% !important;
|
||||
}
|
||||
table[class="body"] .img-responsive {
|
||||
height: auto !important;
|
||||
max-width: 100% !important;
|
||||
width: auto !important;
|
||||
}
|
||||
}
|
||||
/* -------------------------------------
|
||||
PRESERVE THESE STYLES IN THE HEAD
|
||||
------------------------------------- */
|
||||
@media all {
|
||||
.ExternalClass {
|
||||
width: 100%;
|
||||
}
|
||||
.ExternalClass,
|
||||
.ExternalClass p,
|
||||
.ExternalClass span,
|
||||
.ExternalClass font,
|
||||
.ExternalClass td,
|
||||
.ExternalClass div {
|
||||
line-height: 100%;
|
||||
}
|
||||
.apple-link a {
|
||||
color: inherit !important;
|
||||
font-family: inherit !important;
|
||||
font-size: inherit !important;
|
||||
font-weight: inherit !important;
|
||||
line-height: inherit !important;
|
||||
text-decoration: none !important;
|
||||
}
|
||||
.btn-primary table td:hover {
|
||||
background-color: #3498db !important;
|
||||
}
|
||||
.btn-primary a:hover {
|
||||
background-color: #3498db !important;
|
||||
border-color: #3498db !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body
|
||||
class=""
|
||||
style="
|
||||
background-color: #f6f6f6;
|
||||
font-family: sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
font-size: 14px;
|
||||
line-height: 1.4;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
-ms-text-size-adjust: 100%;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
"
|
||||
>
|
||||
<table
|
||||
border="0"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
class="body"
|
||||
style="
|
||||
border-collapse: separate;
|
||||
mso-table-lspace: 0pt;
|
||||
mso-table-rspace: 0pt;
|
||||
width: 100%;
|
||||
background-color: #f6f6f6;
|
||||
"
|
||||
>
|
||||
<tr>
|
||||
<td style="font-family: sans-serif; font-size: 14px; vertical-align: top"> </td>
|
||||
<td
|
||||
class="container"
|
||||
style="
|
||||
font-family: sans-serif;
|
||||
font-size: 14px;
|
||||
vertical-align: top;
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
max-width: 580px;
|
||||
padding: 10px;
|
||||
width: 580px;
|
||||
"
|
||||
>
|
||||
<div
|
||||
class="content"
|
||||
style="box-sizing: border-box; display: block; margin: 0 auto; max-width: 580px; padding: 10px"
|
||||
>
|
||||
<!-- START CENTERED WHITE CONTAINER -->
|
||||
<span
|
||||
class="preheader"
|
||||
style="
|
||||
color: transparent;
|
||||
display: none;
|
||||
height: 0;
|
||||
max-height: 0;
|
||||
max-width: 0;
|
||||
opacity: 0;
|
||||
overflow: hidden;
|
||||
mso-hide: all;
|
||||
visibility: hidden;
|
||||
width: 0;
|
||||
"
|
||||
></span>
|
||||
<table
|
||||
class="main"
|
||||
style="
|
||||
border-collapse: separate;
|
||||
mso-table-lspace: 0pt;
|
||||
mso-table-rspace: 0pt;
|
||||
width: 100%;
|
||||
background: #ffffff;
|
||||
border-radius: 5px;
|
||||
"
|
||||
>
|
||||
<!-- START MAIN CONTENT AREA -->
|
||||
<tr>
|
||||
<td
|
||||
class="wrapper"
|
||||
style="
|
||||
font-family: sans-serif;
|
||||
font-size: 14px;
|
||||
vertical-align: top;
|
||||
box-sizing: border-box;
|
||||
padding: 20px;
|
||||
"
|
||||
>
|
||||
<table
|
||||
border="0"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
style="
|
||||
border-collapse: separate;
|
||||
mso-table-lspace: 0pt;
|
||||
mso-table-rspace: 0pt;
|
||||
width: 100%;
|
||||
"
|
||||
>
|
||||
<tr>
|
||||
<td style="font-family: sans-serif; font-size: 14px; vertical-align: top">
|
||||
<p
|
||||
style="
|
||||
font-family: sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
margin: 0;
|
||||
margin-bottom: 15px;
|
||||
"
|
||||
>
|
||||
Hi there!
|
||||
</p>
|
||||
|
||||
<p
|
||||
style="
|
||||
font-family: sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
margin: 0;
|
||||
margin-bottom: 15px;
|
||||
"
|
||||
>
|
||||
This is just an email to warn you that there was a
|
||||
<strong>new successful login</strong> to your account in Padloc,
|
||||
from {{ location }}.
|
||||
</p>
|
||||
|
||||
<p
|
||||
style="
|
||||
font-family: sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
margin: 0;
|
||||
margin-bottom: 15px;
|
||||
"
|
||||
>
|
||||
If this was you, there's no action necessary, otherwise you might
|
||||
want to make sure your trusted devices haven't been compromised, or
|
||||
to remove potentially compromised devices from your trusted list
|
||||
inside the app.
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- END MAIN CONTENT AREA -->
|
||||
</table>
|
||||
<!-- START FOOTER -->
|
||||
<div class="footer" style="clear: both; margin-top: 10px; text-align: center; width: 100%">
|
||||
<table
|
||||
border="0"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
style="
|
||||
border-collapse: separate;
|
||||
mso-table-lspace: 0pt;
|
||||
mso-table-rspace: 0pt;
|
||||
width: 100%;
|
||||
"
|
||||
>
|
||||
<tr>
|
||||
<td
|
||||
class="content-block"
|
||||
style="
|
||||
font-family: sans-serif;
|
||||
vertical-align: top;
|
||||
padding-bottom: 10px;
|
||||
padding-top: 10px;
|
||||
font-size: 12px;
|
||||
color: #999999;
|
||||
text-align: center;
|
||||
"
|
||||
>
|
||||
<span
|
||||
class="apple-link"
|
||||
style="color: #999999; font-size: 12px; text-align: center"
|
||||
>This email was sent to you by Padloc (https://padloc.app). If you have any
|
||||
questions, please don't hesitate to contact us at support@padloc.app!</span
|
||||
>
|
||||
<!--<br> Don't like these emails? <a href="" style="text-decoration: underline; color: #999999; font-size: 12px; text-align: center;">Unsubscribe</a>.-->
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<!-- END FOOTER -->
|
||||
<!-- END CENTERED WHITE CONTAINER -->
|
||||
</div>
|
||||
</td>
|
||||
<td style="font-family: sans-serif; font-size: 14px; vertical-align: top"> </td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,7 @@
|
|||
Hi there!
|
||||
|
||||
This is just an email to warn you that there was a new successful login to your account in Padloc, from {{ location }}.
|
||||
|
||||
If this was you, there's no action necessary, otherwise you might want to make sure your trusted devices haven't been compromised, or to remove potentially compromised devices from your trusted list inside the app.
|
||||
|
||||
This email was sent to you by Padloc (https://padloc.app). If you have any questions, please don't hesitate to contact us at support@padloc.app!
|
|
@ -321,7 +321,7 @@ Cypress.Commands.add("v3_lock", () => {
|
|||
// Click lock
|
||||
cy.doWithin(["pl-app", "pl-menu"], () => cy.get("pl-icon[icon='lock'].tap").click({ force: true }));
|
||||
|
||||
cy.url().should("include", "/unlock");
|
||||
cy.url({ timeout: 10000 }).should("include", "/unlock");
|
||||
});
|
||||
|
||||
Cypress.Commands.add("v3_unlock", (email: string) => {
|
||||
|
@ -337,5 +337,5 @@ Cypress.Commands.add("v3_unlock", (email: string) => {
|
|||
cy.get("pl-loading-button#unlockButton").click({ force: true });
|
||||
});
|
||||
|
||||
cy.url().should("include", "/items");
|
||||
cy.url({ timeout: 10000 }).should("include", "/items");
|
||||
});
|
||||
|
|
|
@ -153,9 +153,9 @@ export class Audit extends StateMixin(Routing(View)) {
|
|||
}
|
||||
|
||||
if (
|
||||
!app.account?.settings.securityReportWeak &&
|
||||
!app.account?.settings.securityReportReused &&
|
||||
!app.account?.settings.securityReportCompromised
|
||||
!app.account?.settings.securityReport.weakPasswords &&
|
||||
!app.account?.settings.securityReport.reusedPasswords &&
|
||||
!app.account?.settings.securityReport.compromisedPaswords
|
||||
) {
|
||||
return html`
|
||||
<div class="fullbleed centering double-padded text-centering vertical layout">
|
||||
|
@ -175,11 +175,13 @@ export class Audit extends StateMixin(Routing(View)) {
|
|||
const items = this._items;
|
||||
return html`
|
||||
<div class="counts">
|
||||
${app.account?.settings.securityReportWeak ? this._renderSection(items, AuditType.WeakPassword) : ""}
|
||||
${app.account?.settings.securityReportReused
|
||||
${app.account?.settings.securityReport.weakPasswords
|
||||
? this._renderSection(items, AuditType.WeakPassword)
|
||||
: ""}
|
||||
${app.account?.settings.securityReport.reusedPasswords
|
||||
? this._renderSection(items, AuditType.ReusedPassword)
|
||||
: ""}
|
||||
${app.account?.settings.securityReportCompromised
|
||||
${app.account?.settings.securityReport.compromisedPaswords
|
||||
? this._renderSection(items, AuditType.CompromisedPassword)
|
||||
: ""}
|
||||
</div>
|
||||
|
|
|
@ -152,21 +152,23 @@ export class SettingsSecurity extends StateMixin(Routing(LitElement)) {
|
|||
autoLock: (this.renderRoot.querySelector("#autoLockButton") as ToggleButton).active,
|
||||
autoLockDelay: (this.renderRoot.querySelector("#autoLockDelaySlider") as Slider).value,
|
||||
});
|
||||
if (app.account) {
|
||||
app.account.settings.securityReportWeak = (
|
||||
app.updateAccount(async (account) => {
|
||||
account.settings.securityReport.weakPasswords = (
|
||||
this.renderRoot.querySelector("#securityReportWeakToggle") as ToggleButton
|
||||
).active;
|
||||
app.account.settings.securityReportReused = (
|
||||
account.settings.securityReport.reusedPasswords = (
|
||||
this.renderRoot.querySelector("#securityReportReusedToggle") as ToggleButton
|
||||
).active;
|
||||
app.account.settings.securityReportCompromised = (
|
||||
account.settings.securityReport.compromisedPaswords = (
|
||||
this.renderRoot.querySelector("#securityReportCompromisedToggle") as ToggleButton
|
||||
).active;
|
||||
app.account.settings.failedLoginAttemptNotifications = (
|
||||
this.renderRoot.querySelector("#failedLoginAttemptNotificationsToggle") as ToggleButton
|
||||
account.settings.notifications.failedLoginAttempts = (
|
||||
this.renderRoot.querySelector("#failedLoginAttemptsNotificationsToggle") as ToggleButton
|
||||
).active;
|
||||
app.save();
|
||||
}
|
||||
account.settings.notifications.newLogins = (
|
||||
this.renderRoot.querySelector("#newLoginsNotificationsToggle") as ToggleButton
|
||||
).active;
|
||||
});
|
||||
auditVaults();
|
||||
}
|
||||
|
||||
|
@ -268,7 +270,7 @@ export class SettingsSecurity extends StateMixin(Routing(LitElement)) {
|
|||
) {
|
||||
return;
|
||||
}
|
||||
await app.api.revokeSession({ id });
|
||||
await app.api.revokeSession(id);
|
||||
app.fetchAuthInfo();
|
||||
}
|
||||
|
||||
|
@ -699,11 +701,11 @@ export class SettingsSecurity extends StateMixin(Routing(LitElement)) {
|
|||
<pl-toggle-button
|
||||
class="transparent"
|
||||
id="securityReportWeakToggle"
|
||||
.active=${app.account?.settings.securityReportWeak || false}
|
||||
.active=${app.account?.settings.securityReport.weakPasswords || false}
|
||||
.label=${html`<div class="horizontal center-aligning spacing layout">
|
||||
<pl-icon icon="weak"></pl-icon>
|
||||
<div>${$l("Weak Passwords")}</div>
|
||||
</div>`}
|
||||
</div>` as TemplateResult}
|
||||
reverse
|
||||
>
|
||||
</pl-toggle-button>
|
||||
|
@ -713,11 +715,11 @@ export class SettingsSecurity extends StateMixin(Routing(LitElement)) {
|
|||
<pl-toggle-button
|
||||
class="transparent"
|
||||
id="securityReportReusedToggle"
|
||||
.active=${app.account?.settings.securityReportReused || false}
|
||||
.active=${app.account?.settings.securityReport.reusedPasswords || false}
|
||||
.label=${html`<div class="horizontal center-aligning spacing layout">
|
||||
<pl-icon icon="reused"></pl-icon>
|
||||
<div>${$l("Reused Passwords")}</div>
|
||||
</div>`}
|
||||
</div>` as TemplateResult}
|
||||
reverse
|
||||
>
|
||||
</pl-toggle-button>
|
||||
|
@ -727,11 +729,11 @@ export class SettingsSecurity extends StateMixin(Routing(LitElement)) {
|
|||
<pl-toggle-button
|
||||
class="transparent"
|
||||
id="securityReportCompromisedToggle"
|
||||
.active=${app.account?.settings.securityReportCompromised || false}
|
||||
.active=${app.account?.settings.securityReport.compromisedPaswords || false}
|
||||
.label=${html`<div class="horizontal center-aligning spacing layout">
|
||||
<pl-icon icon="compromised"></pl-icon>
|
||||
<div>${$l("Compromised Passwords")}</div>
|
||||
</div>`}
|
||||
</div>` as TemplateResult}
|
||||
reverse
|
||||
>
|
||||
</pl-toggle-button>
|
||||
|
@ -748,12 +750,26 @@ export class SettingsSecurity extends StateMixin(Routing(LitElement)) {
|
|||
<div>
|
||||
<pl-toggle-button
|
||||
class="transparent"
|
||||
id="failedLoginAttemptNotificationsToggle"
|
||||
.active=${app.account?.settings.failedLoginAttemptNotifications || false}
|
||||
id="failedLoginAttemptsNotificationsToggle"
|
||||
.active=${app.account?.settings.notifications.failedLoginAttempts || false}
|
||||
.label=${html`<div class="horizontal center-aligning spacing layout">
|
||||
<pl-icon icon="weak"></pl-icon>
|
||||
<div>${$l("Failed Login Attempt")}</div>
|
||||
</div>`}
|
||||
<div>${$l("Failed Login Attempts")}</div>
|
||||
</div>` as TemplateResult}
|
||||
reverse
|
||||
>
|
||||
</pl-toggle-button>
|
||||
</div>
|
||||
|
||||
<div class="border-top">
|
||||
<pl-toggle-button
|
||||
class="transparent"
|
||||
id="newLoginsNotificationsToggle"
|
||||
.active=${app.account?.settings.notifications.newLogins || false}
|
||||
.label=${html`<div class="horizontal center-aligning spacing layout">
|
||||
<pl-icon icon="weak"></pl-icon>
|
||||
<div>${$l("New Logins (on new or untrusted devices)")}</div>
|
||||
</div>` as TemplateResult}
|
||||
reverse
|
||||
>
|
||||
</pl-toggle-button>
|
||||
|
|
|
@ -13,7 +13,7 @@ export class ToggleButton extends LitElement {
|
|||
reverse: boolean = false;
|
||||
|
||||
@property()
|
||||
label: string | TemplateResult | TemplateResult<1> = "";
|
||||
label: string | TemplateResult = "";
|
||||
|
||||
@query("pl-toggle")
|
||||
_toggle: Toggle;
|
||||
|
|
|
@ -215,8 +215,8 @@ export class Unlock extends StartForm {
|
|||
if (e.code !== ErrorCode.DECRYPTION_FAILED) {
|
||||
throw e;
|
||||
}
|
||||
if (app.account?.settings.failedLoginAttemptNotifications && this._failedCount > 3) {
|
||||
await this.app.logout(true);
|
||||
if (this._failedCount > 3) {
|
||||
await this.app.logout();
|
||||
router.go("login");
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -150,7 +150,7 @@ export async function auditVaults(
|
|||
reusedPasswordItemIds.add(item.id);
|
||||
}
|
||||
|
||||
if (app.account?.settings.securityReportReused) {
|
||||
if (app.account?.settings.securityReport.reusedPasswords) {
|
||||
auditResults.push({
|
||||
type: AuditType.ReusedPassword,
|
||||
fieldIndex: passwordField.fieldIndex,
|
||||
|
@ -160,7 +160,7 @@ export async function auditVaults(
|
|||
vaultResultsFound = true;
|
||||
}
|
||||
|
||||
if (app.account?.settings.securityReportWeak) {
|
||||
if (app.account?.settings.securityReport.weakPasswords) {
|
||||
// Perform weak audit
|
||||
const isThisPasswordWeak = await isPasswordWeak(passwordField.field.value);
|
||||
if (isThisPasswordWeak) {
|
||||
|
@ -179,7 +179,7 @@ export async function auditVaults(
|
|||
}
|
||||
}
|
||||
|
||||
if (app.account?.settings.securityReportCompromised) {
|
||||
if (app.account?.settings.securityReport.compromisedPaswords) {
|
||||
// Perform compromised audit
|
||||
const isPasswordCompromised = await hasPasswordBeenCompromised(passwordHash);
|
||||
if (isPasswordCompromised) {
|
||||
|
|
|
@ -27,16 +27,23 @@ export class AccountSecrets extends Serializable {
|
|||
favorites = new Set<VaultItemID>();
|
||||
}
|
||||
|
||||
/** Various application settings */
|
||||
export class SecurityReportSettings extends Serializable {
|
||||
weakPasswords = true;
|
||||
reusedPasswords = true;
|
||||
compromisedPaswords = true;
|
||||
}
|
||||
|
||||
export class NotificationSettings extends Serializable {
|
||||
failedLoginAttempts = true;
|
||||
newLogins = true;
|
||||
}
|
||||
|
||||
export class AccountSettings extends Serializable {
|
||||
/** Enable checking for weak passwords */
|
||||
securityReportWeak = true;
|
||||
/** Enable checking for reused passwords */
|
||||
securityReportReused = true;
|
||||
/** Enable checking for compromised passwords */
|
||||
securityReportCompromised = true;
|
||||
/** Enable emails for failed login attempts */
|
||||
failedLoginAttemptNotifications = true;
|
||||
@AsSerializable(SecurityReportSettings)
|
||||
securityReport = new SecurityReportSettings();
|
||||
|
||||
@AsSerializable(NotificationSettings)
|
||||
notifications = new NotificationSettings();
|
||||
}
|
||||
|
||||
export const ACCOUNT_NAME_MAX_LENGTH = 100;
|
||||
|
|
|
@ -486,7 +486,7 @@ export class API {
|
|||
* Revoke a [[Session]], effectively logging out any client authenticated with it
|
||||
*/
|
||||
@Handler(String, undefined)
|
||||
revokeSession(_params: { id: SessionID; revokedForFailedAttempts?: boolean }): PromiseWithProgress<void> {
|
||||
revokeSession(_id: SessionID): PromiseWithProgress<void> {
|
||||
throw "Not implemented";
|
||||
}
|
||||
|
||||
|
|
|
@ -713,8 +713,8 @@ export class App {
|
|||
/**
|
||||
* Logs out user and clears all sensitive information
|
||||
*/
|
||||
async logout(revokedForFailedAttempts = false) {
|
||||
await this._logout(revokedForFailedAttempts);
|
||||
async logout() {
|
||||
await this._logout();
|
||||
this.publish();
|
||||
}
|
||||
|
||||
|
@ -723,13 +723,13 @@ export class App {
|
|||
await this._logout();
|
||||
}
|
||||
|
||||
private async _logout(revokedForFailedAttempts = false) {
|
||||
private async _logout() {
|
||||
this._cachedStartCreateSessionResponses.clear();
|
||||
|
||||
// Revoke session
|
||||
try {
|
||||
await this.forgetMasterKey();
|
||||
await this.api.revokeSession({ id: this.state.session!.id, revokedForFailedAttempts });
|
||||
await this.api.revokeSession(this.state.session!.id);
|
||||
} catch (e) {}
|
||||
|
||||
// Reset application state
|
||||
|
@ -842,8 +842,8 @@ export class App {
|
|||
/**
|
||||
* Revokes the given [[Session]]
|
||||
*/
|
||||
async revokeSession({ id }: { id: SessionID }) {
|
||||
await this.api.revokeSession({ id });
|
||||
async revokeSession(id: SessionID) {
|
||||
await this.api.revokeSession(id);
|
||||
await this.fetchAccount();
|
||||
}
|
||||
|
||||
|
|
|
@ -69,6 +69,15 @@ export class FailedLoginAttemptMessage extends Message<{ location: string }> {
|
|||
}
|
||||
}
|
||||
|
||||
export class NewLoginMessage extends Message<{ location: string }> {
|
||||
template = "new-login";
|
||||
|
||||
get title() {
|
||||
const appName = process.env.PL_APP_NAME;
|
||||
return `${appName ? appName + " " : ""}New Login from ${this.data.location})`;
|
||||
}
|
||||
}
|
||||
|
||||
export class PlainMessage extends Message<{ message: string }> {
|
||||
template = "plain";
|
||||
|
||||
|
|
|
@ -49,6 +49,7 @@ import {
|
|||
JoinOrgInviteCompletedMessage,
|
||||
JoinOrgInviteMessage,
|
||||
FailedLoginAttemptMessage,
|
||||
NewLoginMessage,
|
||||
Messenger,
|
||||
} from "./messenger";
|
||||
import { Server as SRPServer, SRPSession } from "./srp";
|
||||
|
@ -599,11 +600,10 @@ export class Controller extends API {
|
|||
this.log("account.createSession", { success: false });
|
||||
++srpState.failedAttempts;
|
||||
await this.storage.save(auth);
|
||||
if (srpState.failedAttempts > 5 && acc.settings.failedLoginAttemptNotifications) {
|
||||
if (srpState.failedAttempts >= 5 && acc.settings.notifications.failedLoginAttempts) {
|
||||
try {
|
||||
const location = this._buildLocationAndDeviceString(this.context.location, this.context.device);
|
||||
|
||||
// Send invite link to invitees email address
|
||||
await this.messenger.send(acc.email, new FailedLoginAttemptMessage({ location }));
|
||||
|
||||
// Remove trusted device, if it was (after email as this can throw if the device was already removed)
|
||||
|
@ -632,13 +632,21 @@ export class Controller extends API {
|
|||
// Persist changes
|
||||
await Promise.all([this.storage.save(session), this.storage.save(acc)]);
|
||||
|
||||
// Add device to trusted devices
|
||||
if (
|
||||
this.context.device &&
|
||||
!auth.trustedDevices.some(({ id }) => id === this.context.device!.id) &&
|
||||
addTrustedDevice
|
||||
) {
|
||||
auth.trustedDevices.push(this.context.device);
|
||||
// Check if device isn't trusted
|
||||
if (this.context.device && !auth.trustedDevices.some(({ id }) => id === this.context.device!.id)) {
|
||||
// Add to trusted devices
|
||||
if (addTrustedDevice) {
|
||||
auth.trustedDevices.push(this.context.device);
|
||||
}
|
||||
|
||||
// Send new login notification (it's a new or untrusted device)
|
||||
if (acc.settings.notifications.newLogins) {
|
||||
try {
|
||||
const location = this._buildLocationAndDeviceString(this.context.location, this.context.device);
|
||||
|
||||
await this.messenger.send(acc.email, new NewLoginMessage({ location }));
|
||||
} catch (e) {}
|
||||
}
|
||||
}
|
||||
await this.storage.save(auth);
|
||||
|
||||
|
@ -654,7 +662,7 @@ export class Controller extends API {
|
|||
return session;
|
||||
}
|
||||
|
||||
async revokeSession({ id, revokedForFailedAttempts }: { id: SessionID; revokedForFailedAttempts?: boolean }) {
|
||||
async revokeSession(id: SessionID) {
|
||||
const { account, auth } = this._requireAuth();
|
||||
|
||||
const session = await this.storage.get(Session, id);
|
||||
|
@ -669,20 +677,6 @@ export class Controller extends API {
|
|||
await Promise.all([this.storage.delete(session), this.storage.save(auth)]);
|
||||
|
||||
this.log("account.revokeSession", { revokedSession: { id, device: session.device } });
|
||||
|
||||
if (revokedForFailedAttempts && account.settings.failedLoginAttemptNotifications) {
|
||||
try {
|
||||
const location = this._buildLocationAndDeviceString(session.info.lastLocation, session.info.device);
|
||||
|
||||
// Send invite link to invitees email address
|
||||
await this.messenger.send(account.email, new FailedLoginAttemptMessage({ location }));
|
||||
|
||||
// Remove trusted device, if it was (after email as this can throw if the device was already removed)
|
||||
if (session.info.device) {
|
||||
await this.removeTrustedDevice(session.info.device.id);
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
}
|
||||
|
||||
async createAccount({
|
||||
|
|
Loading…
Reference in New Issue