Skip to content

Commit

Permalink
support login delay: account editing
Browse files Browse the repository at this point in the history
two options:
* trigger login on account selection
* trigger login on after delay based on the "4-23"-like seconds range
  • Loading branch information
vladimiry committed Mar 27, 2019
1 parent 4a8cb80 commit a2aba08
Show file tree
Hide file tree
Showing 7 changed files with 126 additions and 22 deletions.
28 changes: 17 additions & 11 deletions src/electron-main/api/endpoints-builders/account.ts
Expand Up @@ -11,14 +11,18 @@ export async function buildEndpoints(
ctx: Context,
): Promise<Pick<Endpoints, "addAccount" | "updateAccount" | "changeAccountOrder" | "removeAccount">> {
return {
addAccount: ({type, login, entryUrl, database, credentials, proxy}) => from((async () => {
addAccount: (
{type, login, entryUrl, database, credentials, proxy, loginDelayOnSelect, loginDelaySecondsRange},
) => from((async () => {
const account = {
type,
login,
entryUrl,
database,
credentials,
proxy: proxy || undefined,
proxy,
loginDelayOnSelect,
loginDelaySecondsRange,
} as AccountConfig; // TODO ger rid of "TS as" casting
const settings = await ctx.settingsStore.readExisting();

Expand All @@ -31,19 +35,19 @@ export async function buildEndpoints(
return result;
})()),

// TODO update "updateAccount" api method test (entryUrl, changed credentials structure)
updateAccount: ({login, entryUrl, database, credentials, proxy}) => from((async () => {
updateAccount: (
{login, entryUrl, database, credentials, proxy, loginDelayOnSelect, loginDelaySecondsRange},
) => from((async () => {
const settings = await ctx.settingsStore.readExisting();
const account = pickAccountStrict(settings.accounts, {login});
const {credentials: existingCredentials} = account;

if (typeof database !== "undefined") {
account.database = database;
}
account.database = database;

if (typeof entryUrl !== "undefined") {
account.entryUrl = entryUrl;
if (typeof entryUrl === "undefined") {
throw new Error('"entryUrl" is undefined');
}
account.entryUrl = entryUrl;

if (credentials) {
if ("password" in credentials) {
Expand All @@ -57,10 +61,12 @@ export async function buildEndpoints(
}
}

account.proxy = proxy || undefined;

account.proxy = proxy;
await configureSessionByAccount(pick(["login", "proxy"], account));

account.loginDelayOnSelect = loginDelayOnSelect;
account.loginDelaySecondsRange = loginDelaySecondsRange;

return await ctx.settingsStore.write(settings);
})()),

Expand Down
2 changes: 2 additions & 0 deletions src/electron-main/api/index.spec.ts
Expand Up @@ -46,6 +46,7 @@ const tests: Record<keyof Endpoints, (t: ExecutionContext<TestContext>) => Imple
t.pass(`TODO test "log" endpoint`);
},

// TODO update "updateAccount" api method test (verify more fields)
addAccount: async (t) => {
const {
endpoints,
Expand Down Expand Up @@ -93,6 +94,7 @@ const tests: Record<keyof Endpoints, (t: ExecutionContext<TestContext>) => Imple
initSessionByAccountMock.calledWithExactly(t.context.ctx, initSessionByAccount2Arg, initSessionByAccountOptions);
},

// TODO update "updateAccount" api method test (verify more fields)
updateAccount: async (t) => {
const {
endpoints,
Expand Down
2 changes: 2 additions & 0 deletions src/shared/model/account.ts
Expand Up @@ -10,6 +10,8 @@ export interface GenericAccountConfig<Type extends AccountType, CredentialFields
proxyRules?: string;
proxyBypassRules?: string;
};
loginDelayOnSelect?: boolean;
loginDelaySecondsRange?: { start: number; end: number; };
}

export type AccountConfigProtonmail = GenericAccountConfig<"protonmail", "password" | "twoFactorCode" | "mailPassword">;
Expand Down
3 changes: 2 additions & 1 deletion src/shared/model/container.ts
Expand Up @@ -29,4 +29,5 @@ export interface PasswordChangeContainer extends PasswordFieldContainer, NewPass
export type AccountConfigCreatePatch<T extends AccountType = AccountType> = AccountConfig<T>;

export type AccountConfigUpdatePatch<T extends AccountType = AccountType> = Pick<AccountConfig<T>, "login">
& Partial<Pick<AccountConfig, "login" | "entryUrl" | "database" | "credentials" | "proxy">>;
& Partial<Pick<AccountConfig,
"login" | "entryUrl" | "database" | "credentials" | "proxy" | "loginDelayOnSelect" | "loginDelaySecondsRange">>;
34 changes: 34 additions & 0 deletions src/shared/util.ts
Expand Up @@ -204,3 +204,37 @@ export const getWebViewPartition: (login: AccountConfig<AccountType>["login"]) =

return result;
})();

export const validateLoginDelaySecondsRange: (
loginDelaySecondsRange: string,
) => { validationError: string } | Required<AccountConfig>["loginDelaySecondsRange"] = (() => {
const re = /^(\d+)-(\d+)$/;
const result: typeof validateLoginDelaySecondsRange = (loginDelaySecondsRange) => {
const match = loginDelaySecondsRange.match(re) || [];
const end = Number(match.pop());
const start = Number(match.pop());

if (isNaN(start) || isNaN(end)) {
return {validationError: `Invalid data format, "number-number" format is expected.`};
}
if (start > end) {
return {validationError: `"Start" value is bigger than "end" value.`};
}

return {start, end};
};

return result;
})();

export const parseLoginDelaySecondsRange: (
loginDelaySecondsRange: string,
) => Required<AccountConfig>["loginDelaySecondsRange"] | undefined = (loginDelaySecondsRange) => {
const validation = validateLoginDelaySecondsRange(loginDelaySecondsRange);

if ("validationError" in validation) {
return;
}

return validation;
};
18 changes: 18 additions & 0 deletions src/web/src/app/_options/account-edit.component.html
Expand Up @@ -150,6 +150,24 @@
</button>
<input type="text" class="form-control" formControlName="proxyBypassRules">
</div>
<div class="mt-3">
<label class="d-block pull-left">Login delay range (seconds)</label>
<div class="custom-control custom-switch float-md-right">
<input type="checkbox" class="custom-control-input" formControlName="loginDelayOnSelect" id="loginDelayOnSelectCheckbox">
<label class="custom-control-label" for="loginDelayOnSelectCheckbox">
Login on account selection
</label>
</div>
<input type="text" class="form-control" formControlName="loginDelaySecondsRange" placeholder="15-65"
[ngClass]="controls.loginDelaySecondsRange.dirty
? {'is-invalid': controls.loginDelaySecondsRange.invalid, 'is-valid': controls.loginDelaySecondsRange.valid}
: {}">
<div
*ngIf="controls.loginDelaySecondsRange.invalid && controls.loginDelaySecondsRange.errors?.errorMsg"
[innerText]="controls.loginDelaySecondsRange.errors?.errorMsg"
class="invalid-feedback"
></div>
</div>
</div>
</div>
<div class="clearfix">
Expand Down
61 changes: 51 additions & 10 deletions src/web/src/app/_options/account-edit.component.ts
Expand Up @@ -12,6 +12,7 @@ import {EntryUrlItem} from "src/shared/types";
import {NAVIGATION_ACTIONS, OPTIONS_ACTIONS} from "src/web/src/app/store/actions";
import {OptionsSelectors} from "src/web/src/app/store/selectors";
import {State} from "src/web/src/app/store/reducers/options";
import {validateLoginDelaySecondsRange} from "src/shared/util";

@Component({
selector: "electron-mail-account-edit",
Expand All @@ -27,9 +28,11 @@ export class AccountEditComponent implements OnInit, OnDestroy {
{value: "tutanota", title: "Tutanota"},
];
entryUrlItems: EntryUrlItem[] = [];
controls: Record<keyof Pick<AccountConfig, "type" | "login" | "database" | "entryUrl">
| "proxyRules" | "proxyBypassRules"
| keyof AccountConfigProtonmail["credentials"], AbstractControl> = {
controls: Record<keyof Pick<AccountConfig,
| "type" | "login" | "database" | "entryUrl" | "loginDelayOnSelect" | "loginDelaySecondsRange">
| keyof Pick<Required<Required<AccountConfig>["proxy"]>, "proxyRules" | "proxyBypassRules">
| keyof AccountConfigProtonmail["credentials"],
AbstractControl> = {
type: new FormControl(this.typeValues[0].value, Validators.required),
login: new FormControl(null, Validators.required),
database: new FormControl(null),
Expand All @@ -39,6 +42,30 @@ export class AccountEditComponent implements OnInit, OnDestroy {
password: new FormControl(null),
twoFactorCode: new FormControl(null),
mailPassword: new FormControl(null),
loginDelayOnSelect: new FormControl(null),
loginDelaySecondsRange: new FormControl(
null,
() => {
const control: AbstractControl | undefined = this.controls && this.controls.loginDelaySecondsRange;
const value = control && control.value;

if (!value) {
return null;
}

const validated = value
? validateLoginDelaySecondsRange(value)
: undefined;

if (validated && "validationError" in validated) {
return {
errorMsg: validated.validationError,
};
}

return null;
},
),
};
form = new FormGroup(this.controls);
// account
Expand Down Expand Up @@ -81,19 +108,21 @@ export class AccountEditComponent implements OnInit, OnDestroy {
controls.database.patchValue(account.database);
controls.entryUrl.patchValue(account.entryUrl);

if (account.proxy) {
controls.proxyRules.patchValue(account.proxy.proxyRules);
controls.proxyBypassRules.patchValue(account.proxy.proxyBypassRules);
} else {
controls.proxyRules.patchValue(null);
controls.proxyBypassRules.patchValue(null);
}
controls.proxyRules.patchValue(account.proxy ? account.proxy.proxyRules : null);
controls.proxyBypassRules.patchValue(account.proxy ? account.proxy.proxyBypassRules : null);

controls.password.patchValue(account.credentials.password);
controls.twoFactorCode.patchValue(account.credentials.twoFactorCode);
if (account.type === "protonmail") {
controls.mailPassword.patchValue(account.credentials.mailPassword);
}

controls.loginDelayOnSelect.patchValue(account.loginDelayOnSelect);
controls.loginDelaySecondsRange.patchValue(
account.loginDelaySecondsRange
? `${account.loginDelaySecondsRange.start}-${account.loginDelaySecondsRange.end}`
: undefined,
);
})();
});

Expand Down Expand Up @@ -131,6 +160,18 @@ export class AccountEditComponent implements OnInit, OnDestroy {
twoFactorCode: controls.twoFactorCode.value,
},
...((proxy.proxyRules || proxy.proxyBypassRules) && {proxy}),
loginDelayOnSelect: Boolean(controls.loginDelayOnSelect.value),
loginDelaySecondsRange: (() => {
const validated = this.controls.loginDelaySecondsRange.value
? validateLoginDelaySecondsRange(this.controls.loginDelaySecondsRange.value)
: undefined;

if (validated && "validationError" in validated) {
throw new Error(validated.validationError);
}

return validated;
})(),
};
const accountType: AccountType = account
? account.type
Expand Down

0 comments on commit a2aba08

Please sign in to comment.