web: fix file upload form (#18808)

* web: fix file upload form name mismatch and modal submit promise handling

Fixes the following error:

FileUploadForm.ts:74  POST http://authentik.localhost:9000/api/v3/admin/file/ 405 (Method Not Allowed)
(anonymous) @ fetch.ts:81
fetchApi @ runtime.ts:206
await in fetchApi
request @ runtime.ts:136
await in request
adminFileCreateRaw @ AdminApi.ts:191
adminFileCreate @ AdminApi.ts:206
send @ FileUploadForm.ts:74
submit @ Form.ts:363
(anonymous) @ ModalForm.ts:54
handleEvent @ lit-html.ts:2109
n @ helpers.ts:117Understand this error
Form.ts:403 authentik/forms: API rejected the form submission due to an invalid field that doesn't appear to be in the form. This is likely a bug in authentik. {detail: 'Response returned an error code'}
(anonymous) @ console.ts:39
(anonymous) @ Form.ts:403
Promise.catch
submit @ Form.ts:376
(anonymous) @ ModalForm.ts:54
handleEvent @ lit-html.ts:2109
n @ helpers.ts:117Understand this error
runtime.ts:140 Uncaught (in promise) ResponseError: Response returned an error code
    at mR.request (runtime.ts:140:15)
    at async mR.adminFileCreateRaw (AdminApi.ts:191:26)
    at async mR.adminFileCreate (AdminApi.ts:206:9)

- align file upload rename field with api name so validation errors map correctly
-improve custom filename extension logic to avoid double or incorrect  extensions
- prevent unhandled promise rejections from modal submit click handler and show  missing-form errors to users

* rev

* wip

* Update ModalForm.ts

Signed-off-by: Dominic R <dominic@sdko.org>

* scope better

* fix what it validates against

---------

Signed-off-by: Dominic R <dominic@sdko.org>
This commit is contained in:
Dominic R
2025-12-16 13:37:22 -05:00
committed by GitHub
parent 603820854b
commit eef8e57f6c
+41 -26
View File
@@ -16,18 +16,33 @@ import { createRef, ref } from "lit/directives/ref.js";
// Same regex is used in the backend as well
const VALID_FILE_NAME_PATTERN = /^[a-zA-Z0-9._/-]+$/;
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/source
// This is perfect for the "pattern" attribute
const VALID_FILE_NAME_PATTERN_STRING = VALID_FILE_NAME_PATTERN.source;
// Note: browsers compile `pattern` using the new `v` RegExp flag (Unicode sets). Under `/v`,
// both `/` and `-` must be escaped inside character classes.
const VALID_FILE_NAME_PATTERN_STRING = "^[a-zA-Z0-9._\\/\\-]+$";
function assertValidFileName(fileName: string): void {
if (!VALID_FILE_NAME_PATTERN.test(fileName)) {
throw new Error(
msg("Filename can only contain letters, numbers, dots, hyphens, and underscores"),
msg(
"Filename can only contain letters, numbers, dots, hyphens, underscores, and slashes",
),
);
}
}
function getFileExtension(fileName: string): string {
const lastDot = fileName.lastIndexOf(".");
if (lastDot <= 0) return "";
return fileName.slice(lastDot);
}
function hasBasenameExtension(fileName: string): boolean {
const baseName = fileName.split("/").pop() ?? fileName;
const lastDot = baseName.lastIndexOf(".");
return lastDot > 0;
}
@customElement("ak-file-upload-form")
export class FileUploadForm extends Form<Record<string, unknown>> {
@property({ type: String, useDefault: true })
@@ -57,36 +72,36 @@ export class FileUploadForm extends Form<Record<string, unknown>> {
throw new PreventFormSubmit("Selected file not provided", this);
}
assertValidFileName(this.selectedFile.name);
const api = new AdminApi(DEFAULT_CONFIG);
const customName = typeof data.fileName === "string" ? data.fileName.trim() : "";
const customName = typeof data.name === "string" ? data.name.trim() : "";
// If custom name provided, validate and append original extension
// Only validate the original filename if no custom name is provided
let finalName = this.selectedFile.name;
if (customName) {
assertValidFileName(customName);
const ext = this.selectedFile.name.substring(this.selectedFile.name.lastIndexOf("."));
finalName = customName + ext;
const ext = getFileExtension(this.selectedFile.name);
finalName =
ext && !hasBasenameExtension(customName) ? `${customName}${ext}` : customName;
} else {
assertValidFileName(this.selectedFile.name);
}
return api
.adminFileCreate({
file: this.selectedFile,
name: finalName,
usage: this.usage,
})
.then(() => {
showMessage({
level: MessageLevel.success,
message: msg("File uploaded successfully"),
});
assertValidFileName(finalName);
this.reset();
})
.finally(() => {
this.clearFileInput();
});
await api.adminFileCreate({
file: this.selectedFile,
name: finalName,
usage: this.usage,
});
showMessage({
level: MessageLevel.success,
message: msg("File uploaded successfully"),
});
this.reset();
this.clearFileInput();
}
renderForm() {
@@ -101,7 +116,7 @@ export class FileUploadForm extends Form<Record<string, unknown>> {
@change=${this.#fileChangeListener}
/>
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${msg("File Name")} name="fileName">
<ak-form-element-horizontal label=${msg("File Name")} name="name">
<input
type="text"
class="pf-c-form-control"