appPassword: add expiry

This commit is contained in:
Girish Ramakrishnan
2026-02-12 12:58:50 +01:00
parent 93a0063941
commit e9c3e42aa6
16 changed files with 226 additions and 62 deletions
+42 -14
View File
@@ -4,9 +4,8 @@ import { useI18n } from 'vue-i18n';
const i18n = useI18n();
const t = i18n.t;
import moment from 'moment-timezone';
import { ref, onMounted, useTemplateRef } from 'vue';
import { Button, ClipboardButton, Dialog, SingleSelect, FormGroup, TextInput, TableView, InputDialog, InputGroup } from '@cloudron/pankow';
import { Button, ClipboardButton, DateTimeInput, Dialog, SingleSelect, FormGroup, TextInput, TableView, InputDialog, InputGroup } from '@cloudron/pankow';
import { prettyLongDate } from '@cloudron/pankow/utils';
import ActionBar from './ActionBar.vue';
import Section from './Section.vue';
@@ -35,7 +34,16 @@ const columns = {
sort(a, b) {
if (!a) return 1;
if (!b) return -1;
return moment(a).isBefore(b) ? 1 : -1;
return new Date(a) - new Date(b);
}
},
expiresAt: {
label: t('profile.appPasswords.expires'),
hideMobile: true,
sort(a, b) {
if (!a) return 1;
if (!b) return -1;
return new Date(a) - new Date(b);
}
},
actions: {}
@@ -54,6 +62,8 @@ const addedPassword = ref('');
const passwordName = ref('');
const identifiers = ref([]);
const identifier = ref('');
const expiresAtDate = ref('');
const minExpiresAt = new Date().toISOString().slice(0, 16);
const addError = ref('');
const busy = ref(false);
@@ -62,16 +72,20 @@ async function refresh() {
const [error, result] = await appPasswordsModel.list();
if (error) return console.error(error);
// setup label for the table UI
result.forEach(function (password) {
if (password.identifier === 'mail') return password.label = password.identifier;
const app = appsById[password.identifier];
if (!app) return password.label = password.identifier + ' (App not found)';
for (const password of result) {
if (password.identifier === 'mail') {
password.label = password.identifier;
} else {
const app = appsById[password.identifier];
if (!app) return password.label = password.identifier + ' (App not found)';
const ftp = app.manifest.addons && app.manifest.addons.localstorage && app.manifest.addons.localstorage.ftp;
const labelSuffix = ftp ? ' - SFTP' : '';
password.label = app.label ? app.label + ' (' + app.fqdn + ')' + labelSuffix : app.fqdn + labelSuffix;
});
const ftp = app.manifest.addons && app.manifest.addons.localstorage && app.manifest.addons.localstorage.ftp;
const labelSuffix = ftp ? ' - SFTP' : '';
password.label = app.label ? app.label + ' (' + app.fqdn + ')' + labelSuffix : app.fqdn + labelSuffix;
}
password.expired = password.expiresAt && new Date(password.expiresAt) < new Date();
}
passwords.value = result;
}
@@ -86,6 +100,7 @@ function onReset() {
setTimeout(() => {
passwordName.value = '';
identifier.value = '';
expiresAtDate.value = '';
addedPassword.value = '';
addError.value = '';
busy.value = false;
@@ -100,7 +115,8 @@ async function onSubmit() {
addError.value = '';
addedPassword.value = '';
const [error, result] = await appPasswordsModel.add(identifier.value, passwordName.value);
const expiresAt = expiresAtDate.value ? new Date(expiresAtDate.value).toISOString() : null;
const [error, result] = await appPasswordsModel.add(identifier.value, passwordName.value, expiresAt);
if (error) {
busy.value = false;
addError.value = error.body ? error.body.message : 'Internal error';
@@ -110,6 +126,7 @@ async function onSubmit() {
addedPassword.value = result.password;
passwordName.value = '';
identifier.value = '';
expiresAtDate.value = '';
await refresh();
@@ -197,6 +214,11 @@ onMounted(async () => {
<label>{{ $t('profile.createAppPassword.app') }}</label>
<SingleSelect outline v-model="identifier" :options="identifiers" option-label="label" option-key="id" required/>
</FormGroup>
<FormGroup>
<label for="expiresAt">{{ $t('profile.createAppPassword.expiresAt') }} (optional)</label>
<DateTimeInput id="expiresAt" v-model="expiresAtDate" :min="minExpiresAt"/>
</FormGroup>
</fieldset>
</form>
</div>
@@ -221,7 +243,13 @@ onMounted(async () => {
<br/>
<TableView :columns="columns" :model="passwords" :placeholder="$t('profile.appPasswords.noPasswordsPlaceholder')">
<template #creationTime="password">{{ prettyLongDate(password.creationTime) }}</template>
<template #name="password"><span :class="{ 'text-muted': password.expired }">{{ password.name }}</span></template>
<template #label="password"><span :class="{ 'text-muted': password.expired }">{{ password.label }}</span></template>
<template #creationTime="password"><span :class="{ 'text-muted': password.expired }">{{ prettyLongDate(password.creationTime) }}</span></template>
<template #expiresAt="password">
<span :class="{ 'text-muted': password.expired }" v-if="!password.expiresAt">-</span>
<span :class="{ 'text-muted': password.expired }" v-else>{{ prettyLongDate(password.expiresAt) }}</span>
</template>
<template #actions="password">
<ActionBar :actions="createActionMenu(password)" />
</template>
+2 -2
View File
@@ -18,10 +18,10 @@ function create() {
if (error || result.status !== 200) return [error || result];
return [null, result.body.appPasswords];
},
async add(identifier, name) {
async add(identifier, name, expiresAt) {
let error, result;
try {
result = await fetcher.post(`${API_ORIGIN}/api/v1/app_passwords`, { identifier, name }, { access_token: accessToken });
result = await fetcher.post(`${API_ORIGIN}/api/v1/app_passwords`, { identifier, name, expiresAt }, { access_token: accessToken });
} catch (e) {
error = e;
}