+
@@ -28,12 +28,15 @@ import {
LogInOutlined,
PersonAddAlt1Outlined,
PersonOutlineRound,
+ DataUsageRound,
} from '@vicons/material'
import { useUserStore } from '@/stores/user'
import { useRoute, useRouter } from 'vue-router'
import { useServicesStore } from '@/stores/services'
const userStore = useUserStore()
+
+const router = useRouter()
const route = useRoute()
const hideUserMenu = computed(() => {
@@ -60,6 +63,14 @@ const guestOptions = [
]
const userOptions = computed(() => [
+ {
+ label: 'Usage',
+ key: 'dashboardUsage',
+ icon: () =>
+ h(NIcon, null, {
+ default: () => h(DataUsageRound),
+ }),
+ },
{
label: 'Profile',
key: 'profile',
@@ -67,7 +78,7 @@ const userOptions = computed(() => [
h(NIcon, null, {
default: () => h(PersonOutlineRound),
}),
- }
+ },
])
const servicesStore = useServicesStore()
@@ -83,6 +94,8 @@ function handleGuestMenuSelect(key: string) {
function handleUserMenuSelect(key: string) {
if (key === 'profile') {
window.open(servicesStore.getSerivceUrl('DysonNetwork.Pass', 'accounts/me')!, '_blank')
+ } else {
+ router.push({ name: key })
}
}
diff --git a/DysonNetwork.Drive/Client/src/router/index.ts b/DysonNetwork.Drive/Client/src/router/index.ts
index 8c8d1d5..7980352 100644
--- a/DysonNetwork.Drive/Client/src/router/index.ts
+++ b/DysonNetwork.Drive/Client/src/router/index.ts
@@ -1,5 +1,6 @@
import { createRouter, createWebHistory } from 'vue-router'
import { useUserStore } from '@/stores/user'
+import { useServicesStore } from '@/stores/services'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
@@ -7,26 +8,53 @@ const router = createRouter({
{
path: '/',
name: 'index',
- component: () => import('../views/index.vue')
+ component: () => import('../views/index.vue'),
},
{
path: '/files/:fileId',
name: 'files',
component: () => import('../views/files.vue'),
- }
- ]
+ },
+ {
+ path: '/dashboard',
+ name: 'dashboard',
+ component: () => import('../layouts/dashboard.vue'),
+ meta: { requiresAuth: true },
+ children: [
+ {
+ path: 'usage',
+ name: 'dashboardUsage',
+ component: () => import('../views/dashboard/usage.vue'),
+ meta: { requiresAuth: true },
+ },
+ ],
+ },
+ {
+ path: '/:notFound(.*)',
+ name: 'errorNotFound',
+ component: () => import('../views/not-found.vue'),
+ },
+ ],
})
router.beforeEach(async (to, from, next) => {
const userStore = useUserStore()
+ const servicesStore = useServicesStore()
// Initialize user state if not already initialized
if (!userStore.user && localStorage.getItem('authToken')) {
- await userStore.initialize()
+ await userStore.fetchUser()
}
if (to.matched.some((record) => record.meta.requiresAuth) && !userStore.isAuthenticated) {
- next({ name: 'login', query: { redirect: to.fullPath } })
+ window.open(
+ servicesStore.getSerivceUrl(
+ 'DysonNetwork.Pass',
+ 'login?redirect=' + encodeURIComponent(window.location.href),
+ )!,
+ '_blank',
+ )
+ next('/')
} else {
next()
}
diff --git a/DysonNetwork.Drive/Client/src/stores/user.ts b/DysonNetwork.Drive/Client/src/stores/user.ts
index 62f586f..7dca8a6 100644
--- a/DysonNetwork.Drive/Client/src/stores/user.ts
+++ b/DysonNetwork.Drive/Client/src/stores/user.ts
@@ -11,7 +11,8 @@ export const useUserStore = defineStore('user', () => {
const isAuthenticated = computed(() => !!user.value)
// Actions
- async function fetchUser() {
+ async function fetchUser(reload = true) {
+ if (!reload && user.value) return
isLoading.value = true
error.value = null
try {
@@ -21,9 +22,6 @@ export const useUserStore = defineStore('user', () => {
if (!response.ok) {
// If the token is invalid, clear it and the user state
- if (response.status === 401) {
- logout()
- }
throw new Error('Failed to fetch user information.')
}
@@ -36,13 +34,6 @@ export const useUserStore = defineStore('user', () => {
}
}
- function logout() {
- user.value = null
- localStorage.removeItem('authToken')
- // Optionally, redirect to login page
- // router.push('/login')
- }
-
function initialize() {
const allowedOrigin = import.meta.env.DEV ? window.location.origin : 'https://id.solian.app'
window.addEventListener('message', (event) => {
@@ -69,7 +60,6 @@ export const useUserStore = defineStore('user', () => {
error,
isAuthenticated,
fetchUser,
- logout,
initialize,
}
})
diff --git a/DysonNetwork.Drive/Client/src/types/pool.ts b/DysonNetwork.Drive/Client/src/types/pool.ts
index 740668a..df0f55b 100644
--- a/DysonNetwork.Drive/Client/src/types/pool.ts
+++ b/DysonNetwork.Drive/Client/src/types/pool.ts
@@ -1,35 +1,36 @@
export interface SnFilePool {
- id: string;
- name: string;
- storage_config: StorageConfig;
- billing_config: BillingConfig;
- public_indexable: boolean;
- public_usable: boolean;
- no_optimization: boolean;
- no_metadata: boolean;
- allow_encryption: boolean;
- allow_anonymous: boolean;
- require_privilege: number;
- account_id: null;
- resource_identifier: string;
- created_at: Date;
- updated_at: Date;
- deleted_at: null;
+ id: string
+ name: string
+ description: string
+ storage_config: StorageConfig
+ billing_config: BillingConfig
+ public_indexable: boolean
+ public_usable: boolean
+ no_optimization: boolean
+ no_metadata: boolean
+ allow_encryption: boolean
+ allow_anonymous: boolean
+ require_privilege: number
+ account_id: null
+ resource_identifier: string
+ created_at: Date
+ updated_at: Date
+ deleted_at: null
}
export interface BillingConfig {
- cost_multiplier: number;
+ cost_multiplier: number
}
export interface StorageConfig {
- region: string;
- bucket: string;
- endpoint: string;
- secret_id: string;
- secret_key: string;
- enable_signed: boolean;
- enable_ssl: boolean;
- image_proxy: null;
- access_proxy: null;
- expiration: null;
+ region: string
+ bucket: string
+ endpoint: string
+ secret_id: string
+ secret_key: string
+ enable_signed: boolean
+ enable_ssl: boolean
+ image_proxy: null
+ access_proxy: null
+ expiration: null
}
diff --git a/DysonNetwork.Drive/Client/src/views/dashboard/usage.vue b/DysonNetwork.Drive/Client/src/views/dashboard/usage.vue
new file mode 100644
index 0000000..34f5c8f
--- /dev/null
+++ b/DysonNetwork.Drive/Client/src/views/dashboard/usage.vue
@@ -0,0 +1,9 @@
+
+
+
+
+
diff --git a/DysonNetwork.Drive/Client/src/views/files.vue b/DysonNetwork.Drive/Client/src/views/files.vue
index 12eed1e..3d8d5ba 100644
--- a/DysonNetwork.Drive/Client/src/views/files.vue
+++ b/DysonNetwork.Drive/Client/src/views/files.vue
@@ -143,6 +143,7 @@ import { useRoute } from 'vue-router'
import { computed, onMounted, ref } from 'vue'
import { downloadAndDecryptFile } from './secure'
+import { formatBytes } from './format'
import hljs from 'highlight.js/lib/core'
import json from 'highlight.js/lib/languages/json'
@@ -200,13 +201,4 @@ function downloadFile() {
window.open(fileSource.value, '_blank')
}
}
-
-function formatBytes(bytes: number, decimals = 2): string {
- if (bytes === 0) return '0 Bytes'
- const k = 1024
- const dm = decimals < 0 ? 0 : decimals
- const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
- const i = Math.floor(Math.log(bytes) / Math.log(k))
- return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]
-}
diff --git a/DysonNetwork.Drive/Client/src/views/format.ts b/DysonNetwork.Drive/Client/src/views/format.ts
new file mode 100644
index 0000000..34f0d4f
--- /dev/null
+++ b/DysonNetwork.Drive/Client/src/views/format.ts
@@ -0,0 +1,8 @@
+export function formatBytes(bytes: number, decimals = 2): string {
+ if (bytes === 0) return '0 Bytes'
+ const k = 1024
+ const dm = decimals < 0 ? 0 : decimals
+ const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
+ const i = Math.floor(Math.log(bytes) / Math.log(k))
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]
+}
diff --git a/DysonNetwork.Drive/Client/src/views/index.vue b/DysonNetwork.Drive/Client/src/views/index.vue
index 3f615b4..69add13 100644
--- a/DysonNetwork.Drive/Client/src/views/index.vue
+++ b/DysonNetwork.Drive/Client/src/views/index.vue
@@ -49,7 +49,9 @@
type="password"
class="mb-2"
/>
- Only available for Stellar Program and certian file pool.
+
+ Only available for Stellar Program and certian file pool.
+
@@ -110,11 +112,15 @@ import {
type SelectOption,
type SelectRenderTag,
type UploadFileInfo,
+ useMessage,
+ NDivider,
+ NTooltip,
} from 'naive-ui'
import { computed, h, onMounted, ref } from 'vue'
import { CloudUploadRound } from '@vicons/material'
import { useUserStore } from '@/stores/user'
import type { SnFilePool } from '@/types/pool'
+import { formatBytes } from './format'
import * as tus from 'tus-js-client'
@@ -160,6 +166,42 @@ function renderPoolSelectLabel(option: SelectOption & SnFilePool) {
},
[
h('div', null, [option.name as string]),
+ option.description &&
+ h(
+ 'div',
+ {
+ style: {
+ fontSize: '0.875rem',
+ opacity: '0.75',
+ },
+ },
+ option.description,
+ ),
+ h(
+ 'div',
+ {
+ style: {
+ display: 'flex',
+ marginBottom: '4px',
+ fontSize: '0.75rem',
+ opacity: '0.75',
+ },
+ },
+ [
+ policy.max_file_size && h('span', `Max ${formatBytes(policy.max_file_size)}`),
+ policy.accept_types &&
+ h(
+ NTooltip,
+ {},
+ {
+ trigger: () => h('span', `Accept limited types`),
+ default: () => h('span', policy.accept_types.join(', ')),
+ },
+ ),
+ ].flatMap((el, idx, arr) =>
+ idx < arr.length - 1 ? [el, h(NDivider, { vertical: true })] : [el],
+ ),
+ ),
h(
'div',
{
@@ -228,6 +270,8 @@ const currentFilePool = computed(() => {
return pools.value?.find((pool) => pool.id === filePool.value) ?? null
})
+const messageDisplay = useMessage()
+
function customRequest({
file,
data,
@@ -246,13 +290,21 @@ function customRequest({
retryDelays: [0, 3000, 5000, 10000, 20000],
metadata: {
filename: file.name,
- filetype: file.type ?? 'application/octet-stream',
+ 'content-type': file.type ?? 'application/octet-stream',
},
headers: {
...requestHeaders,
...headers,
},
onError: function (error) {
+ if (error instanceof tus.DetailedError) {
+ const failedBody = error.originalResponse?.getBody()
+ if (failedBody != null)
+ messageDisplay.error(`Upload failed: ${failedBody}`, {
+ duration: 10000,
+ closable: true,
+ })
+ }
console.error('[DRIVE] Upload failed:', error)
onError()
},
@@ -290,8 +342,7 @@ function createThumbnailUrl(
function customDownload(file: UploadFileInfo) {
const { url, name } = file
- if (!url)
- return
+ if (!url) return
window.open(url.replace('/api', ''), '_blank')
}
diff --git a/DysonNetwork.Drive/Client/src/views/not-found.vue b/DysonNetwork.Drive/Client/src/views/not-found.vue
new file mode 100644
index 0000000..b5c8da9
--- /dev/null
+++ b/DysonNetwork.Drive/Client/src/views/not-found.vue
@@ -0,0 +1,16 @@
+
+
+
+
+
diff --git a/DysonNetwork.Drive/Migrations/20250726120323_AddFilePoolDescription.Designer.cs b/DysonNetwork.Drive/Migrations/20250726120323_AddFilePoolDescription.Designer.cs
new file mode 100644
index 0000000..7483097
--- /dev/null
+++ b/DysonNetwork.Drive/Migrations/20250726120323_AddFilePoolDescription.Designer.cs
@@ -0,0 +1,271 @@
+//
+using System;
+using System.Collections.Generic;
+using DysonNetwork.Drive;
+using DysonNetwork.Drive.Storage;
+using DysonNetwork.Shared.Data;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using NodaTime;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+
+#nullable disable
+
+namespace DysonNetwork.Drive.Migrations
+{
+ [DbContext(typeof(AppDatabase))]
+ [Migration("20250726120323_AddFilePoolDescription")]
+ partial class AddFilePoolDescription
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "9.0.7")
+ .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+ NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "postgis");
+ NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+
+ modelBuilder.Entity("DysonNetwork.Drive.Storage.CloudFile", b =>
+ {
+ b.Property("Id")
+ .HasMaxLength(32)
+ .HasColumnType("character varying(32)")
+ .HasColumnName("id");
+
+ b.Property("AccountId")
+ .HasColumnType("uuid")
+ .HasColumnName("account_id");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("created_at");
+
+ b.Property("DeletedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("deleted_at");
+
+ b.Property("Description")
+ .HasMaxLength(4096)
+ .HasColumnType("character varying(4096)")
+ .HasColumnName("description");
+
+ b.Property>("FileMeta")
+ .HasColumnType("jsonb")
+ .HasColumnName("file_meta");
+
+ b.Property("HasCompression")
+ .HasColumnType("boolean")
+ .HasColumnName("has_compression");
+
+ b.Property("HasThumbnail")
+ .HasColumnType("boolean")
+ .HasColumnName("has_thumbnail");
+
+ b.Property("Hash")
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)")
+ .HasColumnName("hash");
+
+ b.Property("IsEncrypted")
+ .HasColumnType("boolean")
+ .HasColumnName("is_encrypted");
+
+ b.Property("IsMarkedRecycle")
+ .HasColumnType("boolean")
+ .HasColumnName("is_marked_recycle");
+
+ b.Property("MimeType")
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)")
+ .HasColumnName("mime_type");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(1024)
+ .HasColumnType("character varying(1024)")
+ .HasColumnName("name");
+
+ b.Property("PoolId")
+ .HasColumnType("uuid")
+ .HasColumnName("pool_id");
+
+ b.Property>("SensitiveMarks")
+ .HasColumnType("jsonb")
+ .HasColumnName("sensitive_marks");
+
+ b.Property("Size")
+ .HasColumnType("bigint")
+ .HasColumnName("size");
+
+ b.Property("StorageId")
+ .HasMaxLength(32)
+ .HasColumnType("character varying(32)")
+ .HasColumnName("storage_id");
+
+ b.Property("StorageUrl")
+ .HasMaxLength(4096)
+ .HasColumnType("character varying(4096)")
+ .HasColumnName("storage_url");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("updated_at");
+
+ b.Property("UploadedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("uploaded_at");
+
+ b.Property("UploadedTo")
+ .HasMaxLength(128)
+ .HasColumnType("character varying(128)")
+ .HasColumnName("uploaded_to");
+
+ b.Property>("UserMeta")
+ .HasColumnType("jsonb")
+ .HasColumnName("user_meta");
+
+ b.HasKey("Id")
+ .HasName("pk_files");
+
+ b.HasIndex("PoolId")
+ .HasDatabaseName("ix_files_pool_id");
+
+ b.ToTable("files", (string)null);
+ });
+
+ modelBuilder.Entity("DysonNetwork.Drive.Storage.CloudFileReference", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid")
+ .HasColumnName("id");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("created_at");
+
+ b.Property("DeletedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("deleted_at");
+
+ b.Property("ExpiredAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("expired_at");
+
+ b.Property("FileId")
+ .IsRequired()
+ .HasMaxLength(32)
+ .HasColumnType("character varying(32)")
+ .HasColumnName("file_id");
+
+ b.Property("ResourceId")
+ .IsRequired()
+ .HasMaxLength(1024)
+ .HasColumnType("character varying(1024)")
+ .HasColumnName("resource_id");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("updated_at");
+
+ b.Property("Usage")
+ .IsRequired()
+ .HasMaxLength(1024)
+ .HasColumnType("character varying(1024)")
+ .HasColumnName("usage");
+
+ b.HasKey("Id")
+ .HasName("pk_file_references");
+
+ b.HasIndex("FileId")
+ .HasDatabaseName("ix_file_references_file_id");
+
+ b.ToTable("file_references", (string)null);
+ });
+
+ modelBuilder.Entity("DysonNetwork.Drive.Storage.FilePool", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid")
+ .HasColumnName("id");
+
+ b.Property("AccountId")
+ .HasColumnType("uuid")
+ .HasColumnName("account_id");
+
+ b.Property("BillingConfig")
+ .IsRequired()
+ .HasColumnType("jsonb")
+ .HasColumnName("billing_config");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("created_at");
+
+ b.Property("DeletedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("deleted_at");
+
+ b.Property("Description")
+ .IsRequired()
+ .HasMaxLength(8192)
+ .HasColumnType("character varying(8192)")
+ .HasColumnName("description");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(1024)
+ .HasColumnType("character varying(1024)")
+ .HasColumnName("name");
+
+ b.Property("PolicyConfig")
+ .IsRequired()
+ .HasColumnType("jsonb")
+ .HasColumnName("policy_config");
+
+ b.Property("StorageConfig")
+ .IsRequired()
+ .HasColumnType("jsonb")
+ .HasColumnName("storage_config");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("updated_at");
+
+ b.HasKey("Id")
+ .HasName("pk_pools");
+
+ b.ToTable("pools", (string)null);
+ });
+
+ modelBuilder.Entity("DysonNetwork.Drive.Storage.CloudFile", b =>
+ {
+ b.HasOne("DysonNetwork.Drive.Storage.FilePool", "Pool")
+ .WithMany()
+ .HasForeignKey("PoolId")
+ .HasConstraintName("fk_files_pools_pool_id");
+
+ b.Navigation("Pool");
+ });
+
+ modelBuilder.Entity("DysonNetwork.Drive.Storage.CloudFileReference", b =>
+ {
+ b.HasOne("DysonNetwork.Drive.Storage.CloudFile", "File")
+ .WithMany()
+ .HasForeignKey("FileId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("fk_file_references_files_file_id");
+
+ b.Navigation("File");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/DysonNetwork.Drive/Migrations/20250726120323_AddFilePoolDescription.cs b/DysonNetwork.Drive/Migrations/20250726120323_AddFilePoolDescription.cs
new file mode 100644
index 0000000..fc30a97
--- /dev/null
+++ b/DysonNetwork.Drive/Migrations/20250726120323_AddFilePoolDescription.cs
@@ -0,0 +1,30 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace DysonNetwork.Drive.Migrations
+{
+ ///
+ public partial class AddFilePoolDescription : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.AddColumn(
+ name: "description",
+ table: "pools",
+ type: "character varying(8192)",
+ maxLength: 8192,
+ nullable: false,
+ defaultValue: "");
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropColumn(
+ name: "description",
+ table: "pools");
+ }
+ }
+}
diff --git a/DysonNetwork.Drive/Migrations/AppDatabaseModelSnapshot.cs b/DysonNetwork.Drive/Migrations/AppDatabaseModelSnapshot.cs
index 44cedb3..f31a577 100644
--- a/DysonNetwork.Drive/Migrations/AppDatabaseModelSnapshot.cs
+++ b/DysonNetwork.Drive/Migrations/AppDatabaseModelSnapshot.cs
@@ -209,6 +209,12 @@ namespace DysonNetwork.Drive.Migrations
.HasColumnType("timestamp with time zone")
.HasColumnName("deleted_at");
+ b.Property("Description")
+ .IsRequired()
+ .HasMaxLength(8192)
+ .HasColumnType("character varying(8192)")
+ .HasColumnName("description");
+
b.Property("Name")
.IsRequired()
.HasMaxLength(1024)
diff --git a/DysonNetwork.Drive/Storage/FilePool.cs b/DysonNetwork.Drive/Storage/FilePool.cs
index 47c3522..186553c 100644
--- a/DysonNetwork.Drive/Storage/FilePool.cs
+++ b/DysonNetwork.Drive/Storage/FilePool.cs
@@ -41,6 +41,7 @@ public class FilePool : ModelBase, IIdentifiedResource
{
public Guid Id { get; set; } = Guid.NewGuid();
[MaxLength(1024)] public string Name { get; set; } = string.Empty;
+ [MaxLength(8192)] public string Description { get; set; } = string.Empty;
[Column(TypeName = "jsonb")] public RemoteStorageConfig StorageConfig { get; set; } = new();
[Column(TypeName = "jsonb")] public BillingConfig BillingConfig { get; set; } = new();
[Column(TypeName = "jsonb")] public PolicyConfig PolicyConfig { get; set; } = new();
diff --git a/DysonNetwork.Drive/Storage/TusService.cs b/DysonNetwork.Drive/Storage/TusService.cs
index 93fd74e..9c0137a 100644
--- a/DysonNetwork.Drive/Storage/TusService.cs
+++ b/DysonNetwork.Drive/Storage/TusService.cs
@@ -156,9 +156,9 @@ public abstract class TusService
var metadata = eventContext.Metadata;
var contentType = metadata.TryGetValue("content-type", out var ct) ? ct.GetString(Encoding.UTF8) : null;
-
+
var scope = eventContext.HttpContext.RequestServices.CreateScope();
-
+
var rejected = false;
var fs = scope.ServiceProvider.GetRequiredService();
@@ -173,9 +173,22 @@ public abstract class TusService
// Do the policy check
var policy = pool!.PolicyConfig;
+ if (!rejected && !pool.PolicyConfig.AllowEncryption)
+ {
+ var encryptPassword = eventContext.HttpContext.Request.Headers["X-FilePass"].FirstOrDefault();
+ if (!string.IsNullOrEmpty(encryptPassword))
+ {
+ eventContext.FailRequest(
+ HttpStatusCode.Forbidden,
+ "File encryption is not allowed in this pool"
+ );
+ rejected = true;
+ }
+ }
+
if (!rejected && policy.AcceptTypes is not null)
{
- if (contentType is null)
+ if (string.IsNullOrEmpty(contentType))
{
eventContext.FailRequest(
HttpStatusCode.BadRequest,
diff --git a/DysonNetwork.Pass/Client/src/router/index.ts b/DysonNetwork.Pass/Client/src/router/index.ts
index c30f0e2..c523217 100644
--- a/DysonNetwork.Pass/Client/src/router/index.ts
+++ b/DysonNetwork.Pass/Client/src/router/index.ts
@@ -34,7 +34,12 @@ const router = createRouter({
name: 'me',
component: () => import('../views/accounts/me.vue'),
meta: { requiresAuth: true }
- }
+ },
+ {
+ path: '/:notFound(.*)',
+ name: 'errorNotFound',
+ component: () => import('../views/not-found.vue'),
+ },
]
})
diff --git a/DysonNetwork.Pass/Client/src/views/not-found.vue b/DysonNetwork.Pass/Client/src/views/not-found.vue
new file mode 100644
index 0000000..b5c8da9
--- /dev/null
+++ b/DysonNetwork.Pass/Client/src/views/not-found.vue
@@ -0,0 +1,16 @@
+
+
+
+
+