/ opencodehub / src / lib / validation.ts
Swadhin Biswas feat(api): add runner, analytics, packages, and security services · 3/6/2026
28f7954
124 lines · 3.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
import { z } from "zod";
import { sanitizeHtml } from "./sanitize";

/**
 * Validation Schemas for OpenCodeHub APIs
 * These schemas prevent injection attacks and ensure data integrity
 */

// Branch Protection
export const BranchProtectionSchema = z.object({
  pattern: z
    .string()
    .min(1)
    .max(255)
    .regex(/^[a-zA-Z0-9\/*_-]+$/, "Invalid branch pattern"),
  active: z.boolean().optional().default(true),
  requiresPr: z.boolean().optional().default(false),
  requiredApprovals: z.number().int().min(0).max(10).optional().default(1),
  dismissStaleReviews: z.boolean().optional().default(false),
  requireCodeOwnerReviews: z.boolean().optional().default(false),
  allowForcePushes: z.boolean().optional().default(false),
});

// Repository Creation
export const CreateRepositorySchema = z.object({
  name: z
    .string()
    .min(1)
    .max(100)
    .regex(/^[a-zA-Z0-9_-]+$/, "Invalid repository name"),
  description: z.string().max(500).optional(),
  visibility: z.enum(["public", "private", "internal"]).default("public"),
  defaultBranch: z.string().min(1).max(255).default("main"),
  hasIssues: z.boolean().optional().default(true),
  hasWiki: z.boolean().optional().default(true),
  hasActions: z.boolean().optional().default(true),
  allowForking: z.boolean().optional().default(true),
});

// User Registration
export const RegisterUserSchema = z.object({
  username: z
    .string()
    .min(3)
    .max(39)
    .regex(/^[a-zA-Z0-9_-]+$/, "Invalid username"),
  email: z.string().email("Invalid email address"),
  password: z.string().min(8).max(100),
  displayName: z.string().max(100).optional(),
});

// Issue Creation
export const CreateIssueSchema = z.object({
  title: z.string().min(1).max(500),
  body: z.string().max(65535).transform(sanitizeHtml).optional(),
});

// Pull Request Creation
export const CreatePullRequestSchema = z.object({
  title: z.string().min(1).max(500),
  body: z.string().max(65535).transform(sanitizeHtml).optional(),
  head: z.string().min(1).max(255),
  base: z.string().min(1).max(255),
});

// Storage Configuration
export const StorageConfigSchema = z.object({
  type: z.enum(["local", "s3", "gcs", "azure", "rclone", "gdrive", "onedrive"]),
  basePath: z.string().min(1).max(1000).optional(),
  bucket: z.string().max(255).optional(),
  region: z.string().max(100).optional(),
  endpoint: z.string().url().optional().or(z.literal("")),
  accessKeyId: z.string().max(255).optional(),
  secretAccessKey: z.string().max(255).optional(),
  rcloneRemote: z.string().max(255).optional(),
});

// General Config
export const GeneralConfigSchema = z.object({
  siteName: z.string().min(1).max(100).optional(),
  siteDescription: z.string().max(500).optional(),
  allowSignups: z.boolean().optional(),
  smtpHost: z.string().max(255).optional(),
  smtpPort: z.number().int().min(1).max(65535).optional(),
  smtpUser: z.string().max(255).optional(),
  smtpPass: z.string().max(255).optional(),
  smtpFrom: z.string().email().optional(),
});

// Webhook Configuration
export const WebhookConfigSchema = z.object({
  url: z
    .string()
    .url()
    .refine(
      (url) => {
        try {
          const parsed = new URL(url);
          if (!["http:", "https:"].includes(parsed.protocol)) return false;
          const h = parsed.hostname.toLowerCase();
          const blocked = [
            "localhost",
            "127.0.0.1",
            "::1",
            "0.0.0.0",
            "169.254.169.254",
            "metadata.google.internal",
          ];
          if (blocked.includes(h)) return false;
          return true;
        } catch {
          return false;
        }
      },
      {
        message:
          "Webhook URL must use HTTP/HTTPS and must not target private/reserved addresses",
      },
    ),
  events: z.array(z.string()).min(1),
  secret: z.string().max(255).optional(),
  active: z.boolean().optional().default(true),
});