aboutsummaryrefslogtreecommitdiff
path: root/src/types
diff options
context:
space:
mode:
authorElizabeth Hunt <me@liz.coffee>2025-12-14 20:36:24 -0800
committerElizabeth Hunt <me@liz.coffee>2025-12-14 20:36:24 -0800
commit6bf57766feb8321f860baf300140563cd9539053 (patch)
treed80ff78c2a7f4dbea79f9ee850542aee1b735ef4 /src/types
downloadposthook-6bf57766feb8321f860baf300140563cd9539053.tar.gz
posthook-6bf57766feb8321f860baf300140563cd9539053.zip
Init
Diffstat (limited to 'src/types')
-rw-r--r--src/types/index.ts78
1 files changed, 78 insertions, 0 deletions
diff --git a/src/types/index.ts b/src/types/index.ts
new file mode 100644
index 0000000..fbfc70d
--- /dev/null
+++ b/src/types/index.ts
@@ -0,0 +1,78 @@
+export enum ContentType {
+ JSON = 'json',
+ FORM = 'form',
+ MULTIPART = 'multipart',
+ TEXT = 'text',
+ RAW = 'raw',
+}
+
+export interface NtfyConfig {
+ enabled: boolean;
+ server?: string;
+ topic?: string;
+}
+
+export interface RouteConfig {
+ name: string;
+ contentType: ContentType;
+ hcaptchaProtected: boolean;
+ hcaptchaSecret?: string;
+ ntfy?: NtfyConfig;
+ requireToken?: boolean;
+}
+
+export interface StoredRequest {
+ timestamp: number;
+ uuid: string;
+ routeName: string;
+ method: string;
+ headers: Record<string, string>;
+ body: unknown;
+ files?: Array<{
+ filename: string;
+ contentType: string;
+ size: number;
+ path: string;
+ }>;
+}
+
+const ROUTE_NAME_PATTERN = /^[a-z0-9][a-z0-9_-]{0,63}$/i;
+
+export function isSafeRouteName(name: unknown): name is string {
+ if (typeof name !== 'string') return false;
+ if (name !== name.trim()) return false;
+ if (name === '.' || name === '..') return false;
+ if (name.includes('/') || name.includes('\\')) return false;
+ return ROUTE_NAME_PATTERN.test(name);
+}
+
+export function isRouteConfig(obj: unknown): obj is RouteConfig {
+ if (typeof obj !== 'object' || obj === null) return false;
+ const r = obj as Record<string, unknown>;
+
+ const validBasic =
+ isSafeRouteName(r.name) &&
+ typeof r.contentType === 'string' &&
+ Object.values(ContentType).includes(r.contentType as ContentType) &&
+ typeof r.hcaptchaProtected === 'boolean' &&
+ (r.hcaptchaProtected === false || typeof r.hcaptchaSecret === 'string');
+
+ if (!validBasic) return false;
+
+ // Validate ntfy config if present
+ if (r.ntfy !== undefined) {
+ if (typeof r.ntfy !== 'object' || r.ntfy === null) return false;
+ const ntfy = r.ntfy as Record<string, unknown>;
+ if (typeof ntfy.enabled !== 'boolean') return false;
+ if (ntfy.enabled && (typeof ntfy.server !== 'string' || typeof ntfy.topic !== 'string')) {
+ return false;
+ }
+ }
+
+ // Validate requireToken if present
+ if (r.requireToken !== undefined && typeof r.requireToken !== 'boolean') {
+ return false;
+ }
+
+ return true;
+}