Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions backend/src/main/java/org/cryptomator/hub/api/DeviceResource.java
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,21 @@ public List<DeviceDto> getSomeLegacy(@QueryParam("ids") List<String> deviceIds)
return legacyDeviceRepo.findAllInList(deviceIds).map(DeviceDto::fromEntity).toList();
}

/**
* @deprecated to be removed in <a href="https://github.com/cryptomator/hub/issues/333">#333</a>
*/
@Deprecated(since = "1.3.0", forRemoval = true)
@GET
@Path("/has-legacy-devices")
@RolesAllowed("admin")
@Produces(MediaType.APPLICATION_JSON)
@Transactional
@Operation(summary = "checks if any user has legacy devices")
@APIResponse(responseCode = "200")
public boolean hasAnyLegacyDevices() {
return legacyDeviceRepo.existsAny();
}

@PUT
@Path("/{deviceId}")
@RolesAllowed("user")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,10 @@ public Stream<LegacyDevice> findAllInList(List<String> ids) {
return find("#LegacyDevice.allInList", Parameters.with("ids", ids)).stream();
}

public boolean existsAny() {
return count() > 0;
}

public void deleteByOwner(String userId) {
delete("#LegacyDevice.deleteByOwner", Parameters.with("userId", userId));
}
Expand Down
5 changes: 5 additions & 0 deletions frontend/src/common/backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,11 @@ class DeviceService {
return axiosAuth.get<DeviceDto[]>(`/devices/legacy-devices?${query}`).then(response => response.data);
}

/** @deprecated since version 1.3.0, to be removed in https://github.com/cryptomator/hub/issues/333 */
public async hasLegacyDevices(): Promise<boolean> {
return axiosAuth.get<boolean>('/devices/has-legacy-devices').then(response => response.data);
}

public async removeDevice(deviceId: string): Promise<AxiosResponse<unknown>> {
return axiosAuth.delete(`/devices/${deviceId}`)
.catch((error) => rethrowAndConvertIfExpected(error, 404));
Expand Down
7 changes: 7 additions & 0 deletions frontend/src/components/AdminSettings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@
</h2>
</div>

<ContentBanner v-if="hasLegacyDevices" type="warning" :title="t('legacyDeviceBanner.title')" class="mt-5">
{{ t('legacyDeviceBanner.admin.description') }}
</ContentBanner>

<div class="space-y-6 mt-5">
<!-- Server Information Section -->
<section class="bg-white px-4 py-5 shadow-sm sm:rounded-lg sm:p-6">
Expand Down Expand Up @@ -239,6 +243,7 @@ import { FetchUpdateError, LatestVersionDto, updateChecker } from '../common/upd
import { debounce } from '../common/util';
import FetchError from './FetchError.vue';
import AdminSettingsEmergencyAccess from './AdminSettingsEmergencyAccess.vue';
import ContentBanner from './ContentBanner.vue';

const { t, d } = useI18n({ useScope: 'global' });
const props = defineProps<{
Expand All @@ -252,6 +257,7 @@ const form = ref<HTMLFormElement>();
const processing = ref(false);
const onFetchError = ref<Error>();
const errorOnFetchingUpdates = ref<boolean>(false);
const hasLegacyDevices = ref<boolean>(false);

onMounted(async () => {
keycloakAdminRealmURL.value = `${cfg.value.keycloakUrl}/admin/${cfg.value.keycloakRealm}/console/`;
Expand All @@ -268,6 +274,7 @@ async function fetchData() {
billing.value = await backend.billing.get();
version.value = await versionDto;
latestVersion.value = await versionAvailable;
hasLegacyDevices.value = await backend.devices.hasLegacyDevices();

const settings = await backend.settings.get();
wotMaxDepth.value = settings.wotMaxDepth;
Expand Down
5 changes: 5 additions & 0 deletions frontend/src/components/LegacyDeviceList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
</div>

<div v-if="me?.devices && me.devices.length > 0">
<ContentBanner type="warning" :title="t('legacyDeviceBanner.title')" class="mb-4">
{{ t('legacyDeviceBanner.user.description') }}
</ContentBanner>

<h2 id="legacyDeviceListTitle" class="text-base font-semibold leading-6 text-gray-900">
{{ t('legacyDeviceList.title') }}
</h2>
Expand Down Expand Up @@ -100,6 +104,7 @@ import { computed, onMounted, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import backend, { DeviceDto, NotFoundError, UserDto } from '../common/backend';
import userdata from '../common/userdata';
import ContentBanner from './ContentBanner.vue';
import FetchError from './FetchError.vue';

const { t, d } = useI18n({ useScope: 'global' });
Expand Down
13 changes: 13 additions & 0 deletions frontend/src/components/VaultList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@

<LicenseAlert v-if="isLicenseViolated && licenseStatus" :is-admin="isAdmin" :license-status="licenseStatus" />

<ContentBanner v-if="anyUserHasLegacyDevices" type="warning" :title="t('legacyDeviceBanner.title')" class="mb-4">
{{ t('legacyDeviceBanner.admin.description') }}
</ContentBanner>

<ContentBanner v-else-if="hasLegacyDevices" type="warning" :title="t('legacyDeviceBanner.title')" class="mb-4">
{{ t('legacyDeviceBanner.user.description') }}
</ContentBanner>

<h2 class="text-2xl font-bold leading-9 text-gray-900 sm:text-3xl sm:truncate">
{{ t('vaultList.title') }}
</h2>
Expand Down Expand Up @@ -171,6 +179,8 @@ const roleOfSelectedVault = computed<VaultRole | 'NONE'>(() => {

const isAdmin = ref<boolean>(false);
const canCreateVaults = ref<boolean>(false);
const hasLegacyDevices = ref<boolean>(false);
const anyUserHasLegacyDevices = ref<boolean>(false);
const licenseStatus = ref<LicenseUserInfoDto>();
const isLicenseViolated = computed(() => {
if (licenseStatus.value) {
Expand Down Expand Up @@ -206,12 +216,15 @@ async function fetchData() {
try {
me.value = await userdata.me;
isAdmin.value = (await auth).hasRole('admin');
const meWithLegacy = await userdata.meWithLegacyDevicesAndLastAccess;
hasLegacyDevices.value = (meWithLegacy.devices?.length ?? 0) > 0;
canCreateVaults.value = (await auth).hasRole('create-vaults');

settings.value = await backend.settings.get();

if (isAdmin.value) {
filterOptions.value['allVaults'] = t('vaultList.filter.entry.allVaults');
anyUserHasLegacyDevices.value = await backend.devices.hasLegacyDevices();
}
accessibleVaults.value = (await backend.vaults.listAccessible()).filter(v => !v.archived).sort((a, b) => a.name.localeCompare(b.name));
ownedVaults.value = (await backend.vaults.listAccessible('OWNER')).sort((a, b) => a.name.localeCompare(b.name));
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/i18n/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,9 @@
"legacyDeviceList.ipAddress": "IP Address",
"legacyDeviceList.lastAccess": "Last Vault Access",
"legacyDeviceList.lastAccess.toolTip": "May be missing for devices accessed with older versions.",
"legacyDeviceBanner.title": "Legacy Devices Will Be Dropped",
"legacyDeviceBanner.user.description": "Your legacy devices will no longer be supported in the next major release. Please update Cryptomator on those devices or remove them.",
"legacyDeviceBanner.admin.description": "One or more users still have legacy devices. Legacy device support will be dropped in the next major release. Please ask affected users to update Cryptomator or remove their legacy devices.",


"manageAccountKey.title": "Account Key",
Expand Down
Loading