Compare commits

...

2 commits

Author SHA1 Message Date
Rodolphe Bréard
db117dab29 Allow to set a default account 2023-09-25 13:06:37 +02:00
Rodolphe Bréard
1c8d6db391 Forbid the separator in the sub-addr name 2023-09-25 11:30:08 +02:00
7 changed files with 43 additions and 15 deletions

View file

@ -14,8 +14,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
### Added
- Possibility to set a default account
### Changed ### Changed
- The style has been entirely reworked using Bootstrap instead of Bulma - The style has been entirely reworked using Bootstrap instead of Bulma
- It is now impossible to include the separator in the dedicated name
## [0.3.0] - 2023-08-25 ## [0.3.0] - 2023-08-25

View file

@ -1,5 +1,5 @@
export function sortAccounts(accounts) { export function sortAccounts(accounts) {
accounts.value.sort((a, b) => { accounts.sort((a, b) => {
const va = `${a.localPart}@${a.domain}`; const va = `${a.localPart}@${a.domain}`;
const vb = `${b.localPart}@${b.domain}`; const vb = `${b.localPart}@${b.domain}`;
return va.localeCompare(vb); return va.localeCompare(vb);

View file

@ -71,6 +71,8 @@
}, },
"manageAccounts": { "manageAccounts": {
"title": "Accounts", "title": "Accounts",
"isDefault": "default",
"setDefault": "Set default",
"delete": "@:invariants.controls.delete", "delete": "@:invariants.controls.delete",
"close": "@:invariants.controls.close" "close": "@:invariants.controls.close"
} }

View file

@ -71,6 +71,8 @@
}, },
"manageAccounts": { "manageAccounts": {
"title": "Comptes", "title": "Comptes",
"isDefault": "défaut",
"setDefault": "Définir par défaut",
"delete": "@:invariants.controls.delete", "delete": "@:invariants.controls.delete",
"close": "@:invariants.controls.close" "close": "@:invariants.controls.close"
} }

View file

@ -62,6 +62,7 @@ const addAccount = () => {
separator: separator.value, separator: separator.value,
domain: domainName.value, domain: domainName.value,
key: key, key: key,
isDefault: false,
}; };
accounts.value.push(newAccount); accounts.value.push(newAccount);
return toMainView(); return toMainView();

View file

@ -11,8 +11,12 @@ import LayoutComponent from '../components/LayoutComponent.vue';
import NavBarComponent from '../components/NavBarComponent.vue'; import NavBarComponent from '../components/NavBarComponent.vue';
const router = useRouter(); const router = useRouter();
const accounts = sortAccounts(useStorage('sake-accounts', [])); const accounts = useStorage('sake-accounts', []);
const selectedAccountId = ref(accounts.value[0].id); const sortedAccounts = computed(() => sortAccounts(accounts.value));
const selectedAccountId = ref((() => {
const def = accounts.value.find((a) => a.isDefault);
return def ? def.id : accounts.value[0].id;
})());
const subAddrName = ref(''); const subAddrName = ref('');
const fromRawAccount = (raw_account) => { const fromRawAccount = (raw_account) => {
@ -29,15 +33,20 @@ const generatedAddr = computed(() => {
const raw_account = accounts.value.find((e) => e.id == selectedAccountId.value); const raw_account = accounts.value.find((e) => e.id == selectedAccountId.value);
if (raw_account) { if (raw_account) {
const account = fromRawAccount(raw_account); const account = fromRawAccount(raw_account);
var hasher = hmac.create(sha256, account.key); if (subAddrName.value.indexOf(account.separator) != -1) {
hasher.update(account.localPart); subAddrName.value = subAddrName.value.replaceAll(account.separator, '');
hasher.update(account.separator); }
hasher.update(subAddrName.value); if (subAddrName.value) {
const mac = hasher.digest(); var hasher = hmac.create(sha256, account.key);
const offset = mac[mac.length - 1] & 0xf; hasher.update(account.localPart);
const reduced_mac = mac.slice(offset, offset + 5); hasher.update(account.separator);
const code = base32Encode(reduced_mac, 'RFC4648', { padding: false }).toLowerCase(); hasher.update(subAddrName.value);
return `${account.localPart}${account.separator}${subAddrName.value}${account.separator}${code}@${account.domain}`; const mac = hasher.digest();
const offset = mac[mac.length - 1] & 0xf;
const reduced_mac = mac.slice(offset, offset + 5);
const code = base32Encode(reduced_mac, 'RFC4648', { padding: false }).toLowerCase();
return `${account.localPart}${account.separator}${subAddrName.value}${account.separator}${code}@${account.domain}`;
}
} }
} }
return ''; return '';
@ -58,7 +67,7 @@ const resetForm = () => {
<div class="mb-3"> <div class="mb-3">
<label class="form-label" for="account-name">{{ $t("main.account") }}</label> <label class="form-label" for="account-name">{{ $t("main.account") }}</label>
<select class="form-select" id="account-name" v-model="selectedAccountId"> <select class="form-select" id="account-name" v-model="selectedAccountId">
<option v-for="account in accounts" :key="account.id" :value="account.id">{{ account.localPart }}@{{ account.domain }}</option> <option v-for="account in sortedAccounts" :key="account.id" :value="account.id">{{ account.localPart }}@{{ account.domain }}</option>
</select> </select>
</div> </div>

View file

@ -1,16 +1,24 @@
<script setup> <script setup>
import { sortAccounts } from '../accounts'; import { sortAccounts } from '../accounts';
import { computed } from 'vue';
import { RouterLink, useRouter } from 'vue-router'; import { RouterLink, useRouter } from 'vue-router';
import { useStorage } from '@vueuse/core'; import { useStorage } from '@vueuse/core';
import ButtonGroupComponent from '../components/ButtonGroupComponent.vue'; import ButtonGroupComponent from '../components/ButtonGroupComponent.vue';
import LayoutComponent from '../components/LayoutComponent.vue'; import LayoutComponent from '../components/LayoutComponent.vue';
const router = useRouter(); const router = useRouter();
const accounts = sortAccounts(useStorage('sake-accounts', [])); const accounts = useStorage('sake-accounts', []);
const sortedAccounts = computed(() => sortAccounts(accounts.value));
const deleteAccount = (accountId) => { const deleteAccount = (accountId) => {
return router.push(`/delete-account/${accountId}`); return router.push(`/delete-account/${accountId}`);
}; };
const setDefaultAccount = (accountId) => {
accounts.value = accounts.value.map((a) => {
a.isDefault = a.id === accountId;
return a;
});
};
const toMainView = () => { const toMainView = () => {
return router.push('/'); return router.push('/');
}; };
@ -22,11 +30,13 @@ const toMainView = () => {
<table class="table"> <table class="table">
<tbody> <tbody>
<tr v-for="account in accounts"> <tr v-for="account in sortedAccounts">
<th class="text-end align-middle"> <th class="text-end align-middle">
<span class="badge text-bg-primary" v-if="account.isDefault">{{ $t("manageAccounts.isDefault") }}</span>
{{ account.localPart }}@{{ account.domain }} {{ account.localPart }}@{{ account.domain }}
</th> </th>
<th> <th>
<button type="button" class="btn btn-primary me-2" @click="setDefaultAccount(account.id)" v-if="!account.isDefault">{{ $t("manageAccounts.setDefault") }}</button>
<button type="button" class="btn btn-danger" @click="deleteAccount(account.id)">{{ $t("manageAccounts.delete") }}</button> <button type="button" class="btn btn-danger" @click="deleteAccount(account.id)">{{ $t("manageAccounts.delete") }}</button>
</th> </th>
</tr> </tr>