@rebasepro/server-core 0.1.2 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/LICENSE +22 -6
  2. package/dist/common/src/util/entities.d.ts +2 -2
  3. package/dist/common/src/util/relations.d.ts +1 -1
  4. package/dist/{index-DXVBFp5V.js → index-BZoAtuqi.js} +6 -2
  5. package/dist/index-BZoAtuqi.js.map +1 -0
  6. package/dist/index.es.js +15851 -15065
  7. package/dist/index.es.js.map +1 -1
  8. package/dist/index.umd.js +15825 -15035
  9. package/dist/index.umd.js.map +1 -1
  10. package/dist/server-core/src/auth/adapter-middleware.d.ts +33 -0
  11. package/dist/server-core/src/auth/admin-routes.d.ts +6 -0
  12. package/dist/server-core/src/auth/auth-overrides.d.ts +139 -0
  13. package/dist/server-core/src/auth/builtin-auth-adapter.d.ts +49 -0
  14. package/dist/server-core/src/auth/crypto-utils.d.ts +16 -0
  15. package/dist/server-core/src/auth/custom-auth-adapter.d.ts +39 -0
  16. package/dist/server-core/src/auth/index.d.ts +7 -0
  17. package/dist/server-core/src/auth/interfaces.d.ts +2 -0
  18. package/dist/server-core/src/auth/middleware.d.ts +18 -0
  19. package/dist/server-core/src/auth/rls-scope.d.ts +31 -0
  20. package/dist/server-core/src/auth/routes.d.ts +7 -1
  21. package/dist/server-core/src/env.d.ts +131 -0
  22. package/dist/server-core/src/index.d.ts +2 -0
  23. package/dist/server-core/src/init.d.ts +62 -3
  24. package/dist/types/src/controllers/auth.d.ts +9 -8
  25. package/dist/types/src/controllers/client.d.ts +3 -0
  26. package/dist/types/src/types/auth_adapter.d.ts +356 -0
  27. package/dist/types/src/types/collections.d.ts +67 -2
  28. package/dist/types/src/types/database_adapter.d.ts +94 -0
  29. package/dist/types/src/types/entity_actions.d.ts +7 -1
  30. package/dist/types/src/types/entity_callbacks.d.ts +1 -1
  31. package/dist/types/src/types/entity_views.d.ts +36 -1
  32. package/dist/types/src/types/index.d.ts +2 -0
  33. package/dist/types/src/types/plugins.d.ts +1 -1
  34. package/dist/types/src/types/properties.d.ts +24 -5
  35. package/dist/types/src/types/property_config.d.ts +6 -2
  36. package/dist/types/src/types/relations.d.ts +1 -1
  37. package/dist/types/src/types/translations.d.ts +8 -0
  38. package/dist/types/src/users/user.d.ts +5 -0
  39. package/package.json +26 -26
  40. package/src/api/errors.ts +1 -1
  41. package/src/api/graphql/graphql-schema-generator.ts +7 -0
  42. package/src/api/openapi-generator.ts +13 -1
  43. package/src/api/rest/api-generator-count.test.ts +14 -12
  44. package/src/api/rest/query-parser.ts +2 -20
  45. package/src/auth/adapter-middleware.ts +83 -0
  46. package/src/auth/admin-routes.ts +36 -43
  47. package/src/auth/auth-overrides.ts +172 -0
  48. package/src/auth/builtin-auth-adapter.ts +384 -0
  49. package/src/auth/crypto-utils.ts +31 -0
  50. package/src/auth/custom-auth-adapter.ts +85 -0
  51. package/src/auth/index.ts +10 -0
  52. package/src/auth/interfaces.ts +2 -0
  53. package/src/auth/jwt.ts +3 -1
  54. package/src/auth/middleware.ts +2 -46
  55. package/src/auth/rls-scope.ts +58 -0
  56. package/src/auth/routes.ts +74 -32
  57. package/src/cron/cron-scheduler.test.ts +9 -9
  58. package/src/cron/cron-scheduler.ts +1 -1
  59. package/src/env.ts +224 -0
  60. package/src/index.ts +4 -0
  61. package/src/init.ts +355 -135
  62. package/src/storage/routes.ts +1 -19
  63. package/src/utils/logging.ts +3 -3
  64. package/test/admin-routes.test.ts +10 -4
  65. package/test/auth-routes.test.ts +2 -2
  66. package/test/backend-hooks-admin.test.ts +32 -12
  67. package/test/custom-auth-adapter.test.ts +177 -0
  68. package/test/env.test.ts +138 -0
  69. package/test/query-parser.test.ts +0 -29
  70. package/tsconfig.json +3 -0
  71. package/dist/index-DXVBFp5V.js.map +0 -1
package/src/env.ts ADDED
@@ -0,0 +1,224 @@
1
+ import { z } from "zod";
2
+ import * as crypto from "crypto";
3
+
4
+ /**
5
+ * Generate a cryptographically secure random secret (hex-encoded).
6
+ * Used as a fallback when secrets are not explicitly configured —
7
+ * avoids the need for hardcoded dev secrets.
8
+ */
9
+ function generateSecret(bytes = 48): string {
10
+ return crypto.randomBytes(bytes).toString("hex");
11
+ }
12
+
13
+ /**
14
+ * Zod coercion helper: transforms `"true"` → `true`, everything else → `false`.
15
+ */
16
+ const boolString = z.enum(["true", "false", ""]).default("false").transform(v => v === "true");
17
+
18
+ /**
19
+ * Zod coercion helper for optional boolean strings.
20
+ */
21
+ const optionalBoolString = z.enum(["true", "false", ""]).optional().transform(v => v === "true");
22
+
23
+ /**
24
+ * Helper to determine if a string is a localhost or loopback address/URL.
25
+ */
26
+ function isLocalhostOrLoopback(value: string): boolean {
27
+ const trimmed = value.trim();
28
+ if (!trimmed) return false;
29
+
30
+ // 1. Try parsing as URL
31
+ try {
32
+ const parsed = new URL(trimmed);
33
+ const host = parsed.hostname.toLowerCase();
34
+ if (
35
+ host === "localhost" ||
36
+ host === "127.0.0.1" ||
37
+ host === "::1" ||
38
+ host.startsWith("127.")
39
+ ) {
40
+ return true;
41
+ }
42
+ } catch {
43
+ // Not a standard URL, or custom protocol that URL class fails to parse
44
+ }
45
+
46
+ // 2. Custom protocol parser fallback (e.g. postgres://, mongodb://, etc.)
47
+ const protocolMatch = trimmed.match(/^[a-zA-Z0-9+-.]+:\/\/(?:[^@/]+@)?(?:\[([^\]]+)\]|([^:/]+))/);
48
+ if (protocolMatch) {
49
+ const host = (protocolMatch[1] || protocolMatch[2] || "").toLowerCase();
50
+ if (
51
+ host === "localhost" ||
52
+ host === "127.0.0.1" ||
53
+ host === "::1" ||
54
+ host.startsWith("127.")
55
+ ) {
56
+ return true;
57
+ }
58
+ }
59
+
60
+ // 3. Plain hostname / host:port checker (e.g. "localhost", "127.0.0.1:5432", "[::1]:6379")
61
+ let plainHost = trimmed.toLowerCase();
62
+ if (plainHost.startsWith("[") && plainHost.includes("]")) {
63
+ const endBracket = plainHost.indexOf("]");
64
+ plainHost = plainHost.slice(1, endBracket);
65
+ } else {
66
+ const colonIndex = plainHost.lastIndexOf(":");
67
+ if (colonIndex !== -1 && plainHost.indexOf(":") === colonIndex) {
68
+ plainHost = plainHost.substring(0, colonIndex);
69
+ }
70
+ }
71
+
72
+ if (
73
+ plainHost === "localhost" ||
74
+ plainHost === "127.0.0.1" ||
75
+ plainHost === "::1" ||
76
+ plainHost.startsWith("127.")
77
+ ) {
78
+ return true;
79
+ }
80
+
81
+ return false;
82
+ }
83
+
84
+ /**
85
+ * The full set of environment variables recognized by a Rebase backend.
86
+ */
87
+ const rebaseEnvSchema = z.object({
88
+ NODE_ENV: z.enum(["development", "production", "test"]).default("development"),
89
+ PORT: z.string().default("3001").transform(Number),
90
+ DATABASE_URL: z.string().url("DATABASE_URL must be a valid URL"),
91
+ ADMIN_CONNECTION_STRING: z.string().url().optional(),
92
+ JWT_SECRET: z.string().min(32, "JWT_SECRET must be at least 32 characters long"),
93
+ JWT_ACCESS_EXPIRES_IN: z.string().default("1h"),
94
+ JWT_REFRESH_EXPIRES_IN: z.string().default("30d"),
95
+ GOOGLE_CLIENT_ID: z.string().optional(),
96
+ GOOGLE_CLIENT_SECRET: z.string().optional(),
97
+ REBASE_SERVICE_KEY: z.string().optional(),
98
+ ALLOW_REGISTRATION: boolString,
99
+ ALLOW_LOCALHOST_IN_PRODUCTION: optionalBoolString,
100
+ CORS_ORIGINS: z.string().optional(),
101
+ FRONTEND_URL: z.string().optional(),
102
+ DB_POOL_MAX: z.string().default("20").transform(Number),
103
+ DB_POOL_IDLE_TIMEOUT: z.string().default("30000").transform(Number),
104
+ DB_POOL_CONNECT_TIMEOUT: z.string().default("10000").transform(Number),
105
+ FORCE_LOCAL_STORAGE: optionalBoolString,
106
+ STORAGE_TYPE: z.enum(["local", "s3"]).default("local"),
107
+ STORAGE_PATH: z.string().optional(),
108
+ S3_BUCKET: z.string().optional(),
109
+ S3_REGION: z.string().optional(),
110
+ S3_ACCESS_KEY_ID: z.string().optional(),
111
+ S3_SECRET_ACCESS_KEY: z.string().optional(),
112
+ S3_ENDPOINT: z.string().url().optional(),
113
+ S3_FORCE_PATH_STYLE: optionalBoolString,
114
+ });
115
+
116
+ /** Inferred type of the validated environment. */
117
+ export type RebaseEnv = z.infer<typeof rebaseEnvSchema>;
118
+
119
+ /**
120
+ * Load and validate the Rebase environment configuration from `process.env`.
121
+ *
122
+ * Call this **after** your `.env` file has been loaded (via `dotenv`, `--env-file`,
123
+ * container injection, etc.). This function does not load `.env` files itself —
124
+ * that is a deployment concern, not a framework concern.
125
+ *
126
+ * Behavior:
127
+ * - Auto-generates ephemeral `JWT_SECRET` and `REBASE_SERVICE_KEY` in
128
+ * non-production mode so developers can start without manual setup.
129
+ * - Blocks auto-generated secrets in production.
130
+ * - Returns a fully typed, validated env object.
131
+ *
132
+ * Use `extend` to add your own typed env variables on top of the base Rebase schema:
133
+ *
134
+ * @example
135
+ * ```ts
136
+ * import dotenv from "dotenv";
137
+ * import { z } from "zod";
138
+ * import { loadEnv } from "@rebasepro/server-core";
139
+ *
140
+ * dotenv.config({ path: "../../.env" });
141
+ *
142
+ * // Basic — just Rebase env vars:
143
+ * export const env = loadEnv();
144
+ *
145
+ * // Extended — add your own typed vars:
146
+ * export const env = loadEnv({
147
+ * extend: z.object({
148
+ * SMTP_HOST: z.string().optional(),
149
+ * SMTP_PORT: z.string().default("587").transform(Number),
150
+ * STRIPE_SECRET_KEY: z.string(),
151
+ * })
152
+ * });
153
+ * // env.SMTP_HOST → string | undefined (fully typed)
154
+ * // env.STRIPE_SECRET_KEY → string (validated, required)
155
+ * ```
156
+ */
157
+ export function loadEnv(): RebaseEnv;
158
+ export function loadEnv<E extends z.AnyZodObject>(options: { extend: E }): RebaseEnv & z.infer<E>;
159
+ export function loadEnv(options?: { extend?: z.AnyZodObject }): Record<string, unknown> {
160
+ // Auto-generate dev secrets before validation so the Zod schema sees valid values.
161
+ const isProduction = process.env.NODE_ENV === "production";
162
+ const autoGeneratedSecrets: string[] = [];
163
+
164
+ if (!isProduction) {
165
+ if (!process.env.JWT_SECRET) {
166
+ process.env.JWT_SECRET = generateSecret();
167
+ autoGeneratedSecrets.push("JWT_SECRET");
168
+ }
169
+ if (!process.env.REBASE_SERVICE_KEY) {
170
+ process.env.REBASE_SERVICE_KEY = generateSecret();
171
+ autoGeneratedSecrets.push("REBASE_SERVICE_KEY");
172
+ }
173
+ }
174
+
175
+ // Merge base schema with user extensions (if provided).
176
+ const combinedSchema = options?.extend
177
+ ? rebaseEnvSchema.merge(options.extend)
178
+ : rebaseEnvSchema;
179
+
180
+ // Validate with production-specific refinements.
181
+ const schema = combinedSchema.superRefine((data, ctx) => {
182
+ const d = data as RebaseEnv & Record<string, unknown>;
183
+ if (d.NODE_ENV === "production" && !d.CORS_ORIGINS && !d.FRONTEND_URL) {
184
+ ctx.addIssue({
185
+ code: z.ZodIssueCode.custom,
186
+ message: "CORS_ORIGINS or FRONTEND_URL must be set in production to secure the API.",
187
+ path: ["CORS_ORIGINS"],
188
+ });
189
+ }
190
+ if (d.NODE_ENV === "production" && autoGeneratedSecrets.length > 0) {
191
+ ctx.addIssue({
192
+ code: z.ZodIssueCode.custom,
193
+ message: `${autoGeneratedSecrets.join(", ")} must be explicitly set in production. ` +
194
+ `Do not rely on auto-generated secrets outside development.`,
195
+ path: [autoGeneratedSecrets[0]],
196
+ });
197
+ }
198
+ if (d.NODE_ENV === "production" && !d.ALLOW_LOCALHOST_IN_PRODUCTION) {
199
+ for (const [key, value] of Object.entries(data)) {
200
+ if (key === "CORS_ORIGINS") continue;
201
+ if (typeof value === "string" && isLocalhostOrLoopback(value)) {
202
+ ctx.addIssue({
203
+ code: z.ZodIssueCode.custom,
204
+ message: `Environment variable ${key} contains a local/loopback URL or host "${value}". Deployed instances must not connect to localhost.`,
205
+ path: [key],
206
+ });
207
+ }
208
+ }
209
+ }
210
+ });
211
+
212
+ const env = schema.parse(process.env);
213
+
214
+ // Warn after successful parse so the server still starts in dev.
215
+ if (autoGeneratedSecrets.length > 0) {
216
+ console.warn(
217
+ `⚠️ Auto-generated secrets for: ${autoGeneratedSecrets.join(", ")}. ` +
218
+ `These are ephemeral — existing tokens will be invalidated on restart. ` +
219
+ `Set them explicitly in .env for persistent sessions.`,
220
+ );
221
+ }
222
+
223
+ return env as Record<string, unknown>;
224
+ }
package/src/index.ts CHANGED
@@ -62,5 +62,9 @@ export * from "./serve-spa";
62
62
  // Dev-mode port resolution (retry on EADDRINUSE)
63
63
  export * from "./utils/dev-port";
64
64
 
65
+ // Environment validation
66
+ export { loadEnv } from "./env";
67
+ export type { RebaseEnv } from "./env";
68
+
65
69
  // Backend bootstrappers (pluggable driver initialization)
66
70