create-velox-app 0.4.2 → 0.4.4

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 (174) hide show
  1. package/dist/cli.js +0 -0
  2. package/dist/index.js +21 -14
  3. package/dist/index.js.map +1 -1
  4. package/dist/templates/auth.d.ts +6 -3
  5. package/dist/templates/auth.d.ts.map +1 -1
  6. package/dist/templates/auth.js +56 -1110
  7. package/dist/templates/auth.js.map +1 -1
  8. package/dist/templates/compiler.d.ts +68 -0
  9. package/dist/templates/compiler.d.ts.map +1 -0
  10. package/dist/templates/compiler.js +149 -0
  11. package/dist/templates/compiler.js.map +1 -0
  12. package/dist/templates/default.d.ts +5 -2
  13. package/dist/templates/default.d.ts.map +1 -1
  14. package/dist/templates/default.js +51 -508
  15. package/dist/templates/default.js.map +1 -1
  16. package/dist/templates/index.d.ts.map +1 -1
  17. package/dist/templates/index.js +19 -10
  18. package/dist/templates/index.js.map +1 -1
  19. package/dist/templates/placeholders.d.ts +70 -0
  20. package/dist/templates/placeholders.d.ts.map +1 -0
  21. package/dist/templates/placeholders.js +145 -0
  22. package/dist/templates/placeholders.js.map +1 -0
  23. package/dist/templates/shared/index.d.ts +9 -0
  24. package/dist/templates/shared/index.d.ts.map +1 -0
  25. package/dist/templates/shared/index.js +9 -0
  26. package/dist/templates/shared/index.js.map +1 -0
  27. package/dist/templates/shared/root.d.ts +14 -0
  28. package/dist/templates/shared/root.d.ts.map +1 -0
  29. package/dist/templates/shared/root.js +43 -0
  30. package/dist/templates/shared/root.js.map +1 -0
  31. package/dist/templates/shared/web-base.d.ts +18 -0
  32. package/dist/templates/shared/web-base.d.ts.map +1 -0
  33. package/dist/templates/shared/web-base.js +63 -0
  34. package/dist/templates/shared/web-base.js.map +1 -0
  35. package/dist/templates/shared/web-styles.d.ts +10 -0
  36. package/dist/templates/shared/web-styles.d.ts.map +1 -0
  37. package/dist/templates/shared/web-styles.js +26 -0
  38. package/dist/templates/shared/web-styles.js.map +1 -0
  39. package/dist/templates/shared.d.ts +3 -14
  40. package/dist/templates/shared.d.ts.map +1 -1
  41. package/dist/templates/shared.js +7 -290
  42. package/dist/templates/shared.js.map +1 -1
  43. package/dist/templates/source/api/config/app.d.ts +13 -0
  44. package/dist/templates/source/api/config/app.d.ts.map +1 -0
  45. package/dist/templates/source/api/config/app.js +14 -0
  46. package/dist/templates/source/api/config/app.js.map +1 -0
  47. package/dist/templates/source/api/config/auth.d.ts +34 -0
  48. package/dist/templates/source/api/config/auth.d.ts.map +1 -0
  49. package/dist/templates/source/api/config/auth.js +165 -0
  50. package/dist/templates/source/api/config/auth.js.map +1 -0
  51. package/dist/templates/source/api/config/index.auth.d.ts +6 -0
  52. package/dist/templates/source/api/config/index.auth.d.ts.map +1 -0
  53. package/dist/templates/source/api/config/index.auth.js +6 -0
  54. package/dist/templates/source/api/config/index.auth.js.map +1 -0
  55. package/dist/templates/source/api/config/index.default.d.ts +5 -0
  56. package/dist/templates/source/api/config/index.default.d.ts.map +1 -0
  57. package/dist/templates/source/api/config/index.default.js +5 -0
  58. package/dist/templates/source/api/config/index.default.js.map +1 -0
  59. package/dist/templates/source/api/database/index.d.ts +9 -0
  60. package/dist/templates/source/api/database/index.d.ts.map +1 -0
  61. package/dist/templates/source/api/database/index.js +18 -0
  62. package/dist/templates/source/api/database/index.js.map +1 -0
  63. package/dist/templates/source/api/index.auth.d.ts +5 -0
  64. package/dist/templates/source/api/index.auth.d.ts.map +1 -0
  65. package/dist/templates/source/api/index.auth.js +59 -0
  66. package/dist/templates/source/api/index.auth.js.map +1 -0
  67. package/dist/templates/source/api/index.default.d.ts +5 -0
  68. package/dist/templates/source/api/index.default.d.ts.map +1 -0
  69. package/dist/templates/source/api/index.default.js +56 -0
  70. package/dist/templates/source/api/index.default.js.map +1 -0
  71. package/dist/templates/source/api/prisma.config.d.ts +9 -0
  72. package/dist/templates/source/api/prisma.config.d.ts.map +1 -0
  73. package/dist/templates/source/api/prisma.config.js +15 -0
  74. package/dist/templates/source/api/prisma.config.js.map +1 -0
  75. package/dist/templates/source/api/procedures/auth.d.ts +14 -0
  76. package/dist/templates/source/api/procedures/auth.d.ts.map +1 -0
  77. package/dist/templates/source/api/procedures/auth.js +221 -0
  78. package/dist/templates/source/api/procedures/auth.js.map +1 -0
  79. package/dist/templates/source/api/procedures/health.d.ts +5 -0
  80. package/dist/templates/source/api/procedures/health.d.ts.map +1 -0
  81. package/dist/templates/source/api/procedures/health.js +21 -0
  82. package/dist/templates/source/api/procedures/health.js.map +1 -0
  83. package/dist/templates/source/api/procedures/index.auth.d.ts +7 -0
  84. package/dist/templates/source/api/procedures/index.auth.d.ts.map +1 -0
  85. package/dist/templates/source/api/procedures/index.auth.js +7 -0
  86. package/dist/templates/source/api/procedures/index.auth.js.map +1 -0
  87. package/dist/templates/source/api/procedures/index.default.d.ts +6 -0
  88. package/dist/templates/source/api/procedures/index.default.d.ts.map +1 -0
  89. package/dist/templates/source/api/procedures/index.default.js +6 -0
  90. package/dist/templates/source/api/procedures/index.default.js.map +1 -0
  91. package/dist/templates/source/api/procedures/users.auth.d.ts +7 -0
  92. package/dist/templates/source/api/procedures/users.auth.d.ts.map +1 -0
  93. package/dist/templates/source/api/procedures/users.auth.js +111 -0
  94. package/dist/templates/source/api/procedures/users.auth.js.map +1 -0
  95. package/dist/templates/source/api/procedures/users.default.d.ts +5 -0
  96. package/dist/templates/source/api/procedures/users.default.d.ts.map +1 -0
  97. package/dist/templates/source/api/procedures/users.default.js +86 -0
  98. package/dist/templates/source/api/procedures/users.default.js.map +1 -0
  99. package/dist/templates/source/api/schemas/index.d.ts +5 -0
  100. package/dist/templates/source/api/schemas/index.d.ts.map +1 -0
  101. package/dist/templates/source/api/schemas/index.js +5 -0
  102. package/dist/templates/source/api/schemas/index.js.map +1 -0
  103. package/dist/templates/source/api/schemas/user.d.ts +11 -0
  104. package/dist/templates/source/api/schemas/user.d.ts.map +1 -0
  105. package/dist/templates/source/api/schemas/user.js +20 -0
  106. package/dist/templates/source/api/schemas/user.js.map +1 -0
  107. package/dist/templates/source/api/tsup.config.d.ts +3 -0
  108. package/dist/templates/source/api/tsup.config.d.ts.map +1 -0
  109. package/dist/templates/source/api/tsup.config.js +10 -0
  110. package/dist/templates/source/api/tsup.config.js.map +1 -0
  111. package/dist/templates/source/web/main.d.ts +9 -0
  112. package/dist/templates/source/web/main.d.ts.map +1 -0
  113. package/dist/templates/source/web/main.js +27 -0
  114. package/dist/templates/source/web/main.js.map +1 -0
  115. package/dist/templates/source/web/routes/__root.d.ts +2 -0
  116. package/dist/templates/source/web/routes/__root.d.ts.map +1 -0
  117. package/dist/templates/source/web/routes/__root.js +28 -0
  118. package/dist/templates/source/web/routes/__root.js.map +1 -0
  119. package/dist/templates/source/web/routes/about.d.ts +2 -0
  120. package/dist/templates/source/web/routes/about.d.ts.map +1 -0
  121. package/dist/templates/source/web/routes/about.js +33 -0
  122. package/dist/templates/source/web/routes/about.js.map +1 -0
  123. package/dist/templates/source/web/routes/index.auth.d.ts +2 -0
  124. package/dist/templates/source/web/routes/index.auth.d.ts.map +1 -0
  125. package/dist/templates/source/web/routes/index.auth.js +159 -0
  126. package/dist/templates/source/web/routes/index.auth.js.map +1 -0
  127. package/dist/templates/source/web/routes/index.default.d.ts +2 -0
  128. package/dist/templates/source/web/routes/index.default.d.ts.map +1 -0
  129. package/dist/templates/source/web/routes/index.default.js +60 -0
  130. package/dist/templates/source/web/routes/index.default.js.map +1 -0
  131. package/dist/templates/source/web/vite.config.d.ts +3 -0
  132. package/dist/templates/source/web/vite.config.d.ts.map +1 -0
  133. package/dist/templates/source/web/vite.config.js +22 -0
  134. package/dist/templates/source/web/vite.config.js.map +1 -0
  135. package/package.json +11 -9
  136. package/src/templates/source/api/config/app.ts +13 -0
  137. package/src/templates/source/api/config/auth.ts +202 -0
  138. package/src/templates/source/api/config/database.ts +22 -0
  139. package/src/templates/source/api/env.auth +22 -0
  140. package/src/templates/source/api/env.default +13 -0
  141. package/src/templates/source/api/index.auth.ts +30 -0
  142. package/src/templates/source/api/index.default.ts +27 -0
  143. package/src/templates/source/api/package.auth.json +40 -0
  144. package/src/templates/source/api/package.default.json +38 -0
  145. package/src/templates/source/api/prisma/schema.auth.prisma +30 -0
  146. package/src/templates/source/api/prisma/schema.default.prisma +28 -0
  147. package/src/templates/source/api/prisma.config.ts +15 -0
  148. package/src/templates/source/api/procedures/auth.ts +285 -0
  149. package/src/templates/source/api/procedures/health.ts +24 -0
  150. package/src/templates/source/api/procedures/users.auth.ts +170 -0
  151. package/src/templates/source/api/procedures/users.default.ts +119 -0
  152. package/src/templates/source/api/schemas/user.ts +29 -0
  153. package/src/templates/source/api/tsconfig.json +12 -0
  154. package/src/templates/source/api/tsup.config.ts +10 -0
  155. package/src/templates/source/root/CLAUDE.auth.md +148 -0
  156. package/src/templates/source/root/CLAUDE.default.md +128 -0
  157. package/src/templates/source/root/README.md +72 -0
  158. package/src/templates/source/root/gitignore +37 -0
  159. package/src/templates/source/root/package.json +17 -0
  160. package/src/templates/source/root/pnpm-workspace.yaml +2 -0
  161. package/src/templates/source/root/tsconfig.json +19 -0
  162. package/src/templates/source/web/App.module.css +282 -0
  163. package/src/templates/source/web/favicon.svg +12 -0
  164. package/src/templates/source/web/index.html +13 -0
  165. package/src/templates/source/web/main.tsx +38 -0
  166. package/src/templates/source/web/package.json +26 -0
  167. package/src/templates/source/web/routes/__root.tsx +31 -0
  168. package/src/templates/source/web/routes/about.tsx +36 -0
  169. package/src/templates/source/web/routes/index.auth.tsx +230 -0
  170. package/src/templates/source/web/routes/index.default.tsx +79 -0
  171. package/src/templates/source/web/styles/global.css +90 -0
  172. package/src/templates/source/web/tsconfig.json +24 -0
  173. package/src/templates/source/web/vite.config.ts +22 -0
  174. package/LICENSE +0 -21
@@ -1,1143 +1,89 @@
1
1
  /**
2
- * Auth Template
2
+ * Auth Template (Full-Stack with Authentication)
3
3
  *
4
- * Full authentication template with JWT auth, guards, rate limiting,
5
- * token rotation, and secure password hashing.
4
+ * Full-stack workspace template with:
5
+ * - apps/api: REST API with JWT authentication, guards, rate limiting
6
+ * - apps/web: React frontend with login/register UI
7
+ *
8
+ * Complete authentication system ready for production.
6
9
  */
7
- import { generateSharedFiles, VELOXTS_VERSION } from './shared.js';
10
+ import { compileTemplate } from './compiler.js';
11
+ import { AUTH_CONFIG } from './placeholders.js';
12
+ import { generateRootFiles, generateWebBaseFiles, generateWebStyleFiles } from './shared/index.js';
8
13
  // ============================================================================
9
- // Package.json
14
+ // API Template Compilation
10
15
  // ============================================================================
11
- function generatePackageJson(config) {
12
- return JSON.stringify({
13
- name: config.projectName,
14
- version: '0.0.1',
15
- description: 'A VeloxTS application with full authentication',
16
- type: 'module',
17
- main: 'dist/index.js',
18
- scripts: {
19
- build: 'tsup',
20
- start: 'node dist/index.js',
21
- dev: 'tsx watch src/index.ts',
22
- 'type-check': 'tsc --noEmit',
23
- clean: "node -e \"require('fs').rmSync('dist',{recursive:true,force:true});require('fs').rmSync('tsconfig.tsbuildinfo',{force:true})\"",
24
- 'db:generate': 'prisma generate',
25
- 'db:push': 'prisma db push',
26
- 'db:studio': 'prisma studio',
27
- postinstall: 'prisma generate',
28
- },
29
- dependencies: {
30
- '@fastify/static': '^8.3.0',
31
- '@prisma/adapter-better-sqlite3': '^7.1.0',
32
- '@prisma/client': '^7.1.0',
33
- '@veloxts/velox': `^${VELOXTS_VERSION}`,
34
- bcrypt: '^5.1.1',
35
- 'better-sqlite3': '^12.5.0',
36
- dotenv: '^17.2.3',
37
- zod: '^3.24.4',
38
- },
39
- devDependencies: {
40
- '@types/bcrypt': '^5.0.2',
41
- prisma: '^7.1.0',
42
- tsup: '^8.5.1',
43
- tsx: '^4.21.0',
44
- typescript: '^5.9.3',
45
- },
46
- }, null, 2);
16
+ function generateApiPackageJson(config) {
17
+ return compileTemplate('api/package.auth.json', config);
47
18
  }
48
- // ============================================================================
49
- // Environment Files
50
- // ============================================================================
51
- function generateEnvExample() {
52
- return `# Database URL
53
- # SQLite (local development):
54
- DATABASE_URL="file:./dev.db"
55
- # PostgreSQL (production):
56
- # DATABASE_URL="postgresql://user:password@localhost:5432/myapp"
57
-
58
- # Server Configuration
59
- PORT=3210
60
- HOST=0.0.0.0
61
- NODE_ENV=development
62
-
63
- # API Configuration
64
- API_PREFIX=/api
65
-
66
- # ============================================================================
67
- # Authentication (REQUIRED for production)
68
- # ============================================================================
69
- # Generate secrets with: openssl rand -base64 64
70
- #
71
- # JWT_SECRET=<your-access-token-secret>
72
- # JWT_REFRESH_SECRET=<your-refresh-token-secret>
73
- #
74
- # NOTE: In development mode, temporary secrets will be generated with a warning.
75
- # Always set these in production!
76
-
77
- # ============================================================================
78
- # Session Authentication (Alternative to JWT)
79
- # ============================================================================
80
- # If using cookie-based sessions instead of JWT, configure these:
81
- #
82
- # SESSION_SECRET=<your-session-secret>
83
- #
84
- # Generate with: openssl rand -base64 32
85
- `;
19
+ function generateApiTsConfig() {
20
+ return compileTemplate('api/tsconfig.json', AUTH_CONFIG);
86
21
  }
87
- // ============================================================================
88
- // Prisma Schema (with password field)
89
- // ============================================================================
90
- function generatePrismaSchema() {
91
- return `// Prisma Schema
92
- //
93
- // This schema defines the database structure with authentication support.
94
- // Using SQLite for simplicity - easily swap to PostgreSQL for production.
95
-
96
- generator client {
97
- provider = "prisma-client"
98
- output = "../src/generated/prisma"
22
+ function generateApiTsupConfig() {
23
+ return compileTemplate('api/tsup.config.ts', AUTH_CONFIG);
99
24
  }
100
-
101
- datasource db {
102
- provider = "sqlite"
25
+ function generateEnvExample(config) {
26
+ return compileTemplate('api/env.auth', config);
103
27
  }
104
-
105
- // ============================================================================
106
- // User Model
107
- // ============================================================================
108
-
109
- /// User model with authentication support
110
- model User {
111
- id String @id @default(uuid())
112
- name String
113
- email String @unique
114
- password String? // Hashed password (optional for social auth)
115
- roles String @default("[\\"user\\"]") // JSON array of roles
116
- createdAt DateTime @default(now())
117
- updatedAt DateTime @updatedAt
118
-
119
- @@map("users")
28
+ function generatePrismaSchema() {
29
+ return compileTemplate('api/prisma/schema.auth.prisma', AUTH_CONFIG);
120
30
  }
121
- `;
31
+ function generatePrismaConfig() {
32
+ return compileTemplate('api/prisma.config.ts', AUTH_CONFIG);
122
33
  }
123
- // ============================================================================
124
- // Auth Config
125
- // ============================================================================
126
34
  function generateAuthConfig() {
127
- return `/**
128
- * Authentication Configuration
129
- *
130
- * JWT-based authentication configuration.
131
- *
132
- * SECURITY: JWT secrets are required from environment variables.
133
- * The app will fail to start in production without them.
134
- */
135
-
136
- import type { AuthPluginOptions } from '@veloxts/velox';
137
-
138
- import { prisma } from '../database/index.js';
139
-
140
- // ============================================================================
141
- // Environment Variable Validation
142
- // ============================================================================
143
-
144
- /**
145
- * Gets required JWT secrets from environment variables.
146
- * Throws a clear error in production if secrets are not configured.
147
- */
148
- function getRequiredSecrets(): { jwtSecret: string; refreshSecret: string } {
149
- const jwtSecret = process.env.JWT_SECRET;
150
- const refreshSecret = process.env.JWT_REFRESH_SECRET;
151
-
152
- const isDevelopment = process.env.NODE_ENV !== 'production';
153
-
154
- if (!jwtSecret || !refreshSecret) {
155
- if (isDevelopment) {
156
- console.warn(
157
- '\\n' +
158
- '='.repeat(70) +
159
- '\\n' +
160
- ' WARNING: JWT secrets not configured!\\n' +
161
- ' Using temporary development secrets. DO NOT USE IN PRODUCTION!\\n' +
162
- '\\n' +
163
- ' To configure secrets, add to .env:\\n' +
164
- ' JWT_SECRET=<generate with: openssl rand -base64 64>\\n' +
165
- ' JWT_REFRESH_SECRET=<generate with: openssl rand -base64 64>\\n' +
166
- '='.repeat(70) +
167
- '\\n'
168
- );
169
- return {
170
- jwtSecret:
171
- jwtSecret || \`dev-only-jwt-secret-\${Math.random().toString(36).substring(2).repeat(4)}\`,
172
- refreshSecret:
173
- refreshSecret ||
174
- \`dev-only-refresh-secret-\${Math.random().toString(36).substring(2).repeat(4)}\`,
175
- };
176
- }
177
-
178
- throw new Error(
179
- '\\n' +
180
- 'CRITICAL: JWT secrets are required but not configured.\\n' +
181
- '\\n' +
182
- 'Required environment variables:\\n' +
183
- ' - JWT_SECRET: Secret for signing access tokens (64+ characters)\\n' +
184
- ' - JWT_REFRESH_SECRET: Secret for signing refresh tokens (64+ characters)\\n' +
185
- '\\n' +
186
- 'Generate secure secrets with:\\n' +
187
- ' openssl rand -base64 64\\n' +
188
- '\\n' +
189
- 'Add them to your environment or .env file before starting the server.\\n'
190
- );
191
- }
192
-
193
- return { jwtSecret, refreshSecret };
194
- }
195
-
196
- // ============================================================================
197
- // Token Revocation Store
198
- // ============================================================================
199
-
200
- /**
201
- * In-memory token revocation store.
202
- *
203
- * PRODUCTION NOTE: Replace with Redis or database-backed store for:
204
- * - Persistence across server restarts
205
- * - Horizontal scaling (multiple server instances)
206
- */
207
- class InMemoryTokenStore {
208
- private revokedTokens: Map<string, number> = new Map();
209
- private usedRefreshTokens: Map<string, string> = new Map();
210
- private cleanupInterval: NodeJS.Timeout | null = null;
211
-
212
- constructor() {
213
- this.cleanupInterval = setInterval(() => this.cleanup(), 5 * 60 * 1000);
214
- }
215
-
216
- revoke(jti: string, expiresInMs: number = 7 * 24 * 60 * 60 * 1000): void {
217
- this.revokedTokens.set(jti, Date.now() + expiresInMs);
218
- }
219
-
220
- isRevoked(jti: string): boolean {
221
- const expiry = this.revokedTokens.get(jti);
222
- if (!expiry) return false;
223
- if (Date.now() > expiry) {
224
- this.revokedTokens.delete(jti);
225
- return false;
226
- }
227
- return true;
228
- }
229
-
230
- markRefreshTokenUsed(jti: string, userId: string): void {
231
- this.usedRefreshTokens.set(jti, userId);
232
- setTimeout(() => this.usedRefreshTokens.delete(jti), 7 * 24 * 60 * 60 * 1000);
233
- }
234
-
235
- isRefreshTokenUsed(jti: string): string | undefined {
236
- return this.usedRefreshTokens.get(jti);
237
- }
238
-
239
- revokeAllUserTokens(userId: string): void {
240
- console.warn(
241
- \`[Security] Token reuse detected for user \${userId}. \` +
242
- 'All tokens should be revoked. Implement proper user->token mapping for production.'
243
- );
244
- }
245
-
246
- private cleanup(): void {
247
- const now = Date.now();
248
- for (const [jti, expiry] of this.revokedTokens.entries()) {
249
- if (now > expiry) {
250
- this.revokedTokens.delete(jti);
251
- }
252
- }
253
- }
254
- }
255
-
256
- export const tokenStore = new InMemoryTokenStore();
257
-
258
- // ============================================================================
259
- // Role Parsing
260
- // ============================================================================
261
-
262
- const ALLOWED_ROLES = ['user', 'admin', 'moderator', 'editor'] as const;
263
-
264
- export function parseUserRoles(rolesJson: string | null): string[] {
265
- if (!rolesJson) return ['user'];
266
-
267
- try {
268
- const parsed: unknown = JSON.parse(rolesJson);
269
-
270
- if (!Array.isArray(parsed)) {
271
- return ['user'];
272
- }
273
-
274
- const validRoles = parsed
275
- .filter((role): role is string => typeof role === 'string')
276
- .filter((role) => ALLOWED_ROLES.includes(role as (typeof ALLOWED_ROLES)[number]));
277
-
278
- return validRoles.length > 0 ? validRoles : ['user'];
279
- } catch {
280
- return ['user'];
281
- }
282
- }
283
-
284
- // ============================================================================
285
- // User Loader
286
- // ============================================================================
287
-
288
- async function userLoader(userId: string) {
289
- const user = await prisma.user.findUnique({
290
- where: { id: userId },
291
- });
292
-
293
- if (!user) return null;
294
-
295
- return {
296
- id: user.id,
297
- email: user.email,
298
- name: user.name,
299
- roles: parseUserRoles(user.roles),
300
- };
35
+ return compileTemplate('api/config/auth.ts', AUTH_CONFIG);
301
36
  }
302
-
303
- // ============================================================================
304
- // Auth Configuration
305
- // ============================================================================
306
-
307
- export function createAuthConfig(): AuthPluginOptions {
308
- const { jwtSecret, refreshSecret } = getRequiredSecrets();
309
-
310
- return {
311
- jwt: {
312
- secret: jwtSecret,
313
- refreshSecret: refreshSecret,
314
- accessTokenExpiry: '15m',
315
- refreshTokenExpiry: '7d',
316
- issuer: 'velox-app',
317
- audience: 'velox-app-client',
318
- },
319
- userLoader,
320
- isTokenRevoked: async (jti: string) => tokenStore.isRevoked(jti),
321
- rateLimit: {
322
- max: 100,
323
- windowMs: 60000,
324
- },
325
- };
326
- }
327
-
328
- export const authConfig = createAuthConfig();
329
- `;
330
- }
331
- function generateConfigIndexWithAuth() {
332
- return `/**
333
- * Configuration Exports
334
- */
335
-
336
- export * from './app.js';
337
- export * from './auth.js';
338
- `;
37
+ function generateConfigApp(config) {
38
+ return compileTemplate('api/config/app.ts', config);
339
39
  }
340
- // ============================================================================
341
- // Auth Procedures
342
- // ============================================================================
343
40
  function generateAuthProcedures() {
344
- return `/**
345
- * Auth Procedures
346
- *
347
- * Authentication procedures for user registration, login, and token management.
348
- *
349
- * REST Endpoints:
350
- * - POST /auth/register - Create new account
351
- * - POST /auth/login - Authenticate and get tokens
352
- * - POST /auth/refresh - Refresh access token
353
- * - POST /auth/logout - Revoke current token
354
- * - GET /auth/me - Get current user (protected)
355
- */
356
-
357
- import {
358
- AuthError,
359
- authenticated,
360
- createAuthRateLimiter,
361
- hashPassword,
362
- jwtManager,
363
- verifyPassword,
364
- defineProcedures,
365
- procedure,
366
- z,
367
- } from '@veloxts/velox';
368
-
369
- import { authConfig, parseUserRoles, tokenStore } from '../config/index.js';
370
- import { prisma } from '../database/index.js';
371
-
372
- // ============================================================================
373
- // Rate Limiter
374
- // ============================================================================
375
-
376
- const rateLimiter = createAuthRateLimiter({
377
- login: {
378
- maxAttempts: 5,
379
- windowMs: 15 * 60 * 1000,
380
- lockoutDurationMs: 15 * 60 * 1000,
381
- progressiveBackoff: true,
382
- },
383
- register: {
384
- maxAttempts: 3,
385
- windowMs: 60 * 60 * 1000,
386
- lockoutDurationMs: 60 * 60 * 1000,
387
- },
388
- refresh: {
389
- maxAttempts: 10,
390
- windowMs: 60 * 1000,
391
- lockoutDurationMs: 60 * 1000,
392
- },
393
- });
394
-
395
- // ============================================================================
396
- // Password Blacklist
397
- // ============================================================================
398
-
399
- const COMMON_PASSWORDS = new Set([
400
- 'password', 'password123', '12345678', '123456789',
401
- 'qwerty123', 'letmein', 'welcome', 'admin123',
402
- ]);
403
-
404
- // ============================================================================
405
- // Schemas
406
- // ============================================================================
407
-
408
- const PasswordSchema = z
409
- .string()
410
- .min(12, 'Password must be at least 12 characters')
411
- .max(128, 'Password must not exceed 128 characters')
412
- .refine((pwd) => /[a-z]/.test(pwd), 'Password must contain at least one lowercase letter')
413
- .refine((pwd) => /[A-Z]/.test(pwd), 'Password must contain at least one uppercase letter')
414
- .refine((pwd) => /[0-9]/.test(pwd), 'Password must contain at least one number')
415
- .refine(
416
- (pwd) => !COMMON_PASSWORDS.has(pwd.toLowerCase()),
417
- 'Password is too common. Please choose a stronger password.'
418
- );
419
-
420
- const EmailSchema = z
421
- .string()
422
- .email('Invalid email address')
423
- .transform((email) => email.toLowerCase().trim());
424
-
425
- const RegisterInput = z.object({
426
- name: z.string().min(2).max(100).trim(),
427
- email: EmailSchema,
428
- password: PasswordSchema,
429
- });
430
-
431
- const LoginInput = z.object({
432
- email: EmailSchema,
433
- password: z.string().min(1),
434
- });
435
-
436
- const RefreshInput = z.object({
437
- refreshToken: z.string(),
438
- });
439
-
440
- const TokenResponse = z.object({
441
- accessToken: z.string(),
442
- refreshToken: z.string(),
443
- expiresIn: z.number(),
444
- tokenType: z.literal('Bearer'),
445
- });
446
-
447
- const UserResponse = z.object({
448
- id: z.string(),
449
- name: z.string(),
450
- email: z.string(),
451
- roles: z.array(z.string()),
452
- });
453
-
454
- const LogoutResponse = z.object({
455
- success: z.boolean(),
456
- message: z.string(),
457
- });
458
-
459
- // ============================================================================
460
- // JWT Manager
461
- // ============================================================================
462
-
463
- const jwt = jwtManager(authConfig.jwt);
464
-
465
- // Dummy hash for timing attack prevention
466
- const DUMMY_HASH = '$2b$12$LQv3c1yqBWVHxkd0LHAkCOYz6TtxMQJqhN8/X4.uy7dPSSXB5G6Uy';
467
-
468
- // ============================================================================
469
- // Auth Procedures
470
- // ============================================================================
471
-
472
- export const authProcedures = defineProcedures('auth', {
473
- register: procedure()
474
- .rest({ method: 'POST', path: '/auth/register' })
475
- .use(rateLimiter.register())
476
- .input(RegisterInput)
477
- .output(TokenResponse)
478
- .mutation(async ({ input }) => {
479
- const normalizedEmail = input.email.toLowerCase().trim();
480
-
481
- const existing = await prisma.user.findUnique({
482
- where: { email: normalizedEmail },
483
- });
484
-
485
- if (existing) {
486
- throw new AuthError(
487
- 'Registration failed. If this email is not already registered, please try again.',
488
- 400,
489
- 'REGISTRATION_FAILED'
490
- );
491
- }
492
-
493
- const hashedPassword = await hashPassword(input.password);
494
-
495
- const user = await prisma.user.create({
496
- data: {
497
- name: input.name.trim(),
498
- email: normalizedEmail,
499
- password: hashedPassword,
500
- roles: JSON.stringify(['user']),
501
- },
502
- });
503
-
504
- return jwt.createTokenPair({
505
- id: user.id,
506
- email: user.email,
507
- roles: ['user'],
508
- });
509
- }),
510
-
511
- login: procedure()
512
- .rest({ method: 'POST', path: '/auth/login' })
513
- .use(
514
- rateLimiter.login((ctx) => {
515
- const input = ctx.input as { email?: string } | undefined;
516
- return input?.email?.toLowerCase() ?? '';
517
- })
518
- )
519
- .input(LoginInput)
520
- .output(TokenResponse)
521
- .mutation(async ({ input }) => {
522
- const normalizedEmail = input.email.toLowerCase().trim();
523
-
524
- const user = await prisma.user.findUnique({
525
- where: { email: normalizedEmail },
526
- });
527
-
528
- const hashToVerify = user?.password || DUMMY_HASH;
529
- const isValid = await verifyPassword(input.password, hashToVerify);
530
-
531
- if (!user || !user.password || !isValid) {
532
- throw new AuthError('Invalid email or password', 401, 'INVALID_CREDENTIALS');
533
- }
534
-
535
- const roles = parseUserRoles(user.roles);
536
-
537
- return jwt.createTokenPair({
538
- id: user.id,
539
- email: user.email,
540
- roles,
541
- });
542
- }),
543
-
544
- refresh: procedure()
545
- .rest({ method: 'POST', path: '/auth/refresh' })
546
- .use(rateLimiter.refresh())
547
- .input(RefreshInput)
548
- .output(TokenResponse)
549
- .mutation(async ({ input }) => {
550
- try {
551
- const payload = jwt.verifyToken(input.refreshToken);
552
-
553
- if (payload.type !== 'refresh') {
554
- throw new AuthError('Invalid token type', 401, 'INVALID_TOKEN_TYPE');
555
- }
556
-
557
- if (payload.jti && tokenStore.isRevoked(payload.jti)) {
558
- throw new AuthError('Token has been revoked', 401, 'TOKEN_REVOKED');
559
- }
560
-
561
- if (payload.jti) {
562
- const previousUserId = tokenStore.isRefreshTokenUsed(payload.jti);
563
- if (previousUserId) {
564
- tokenStore.revokeAllUserTokens(previousUserId);
565
- throw new AuthError(
566
- 'Security alert: Refresh token reuse detected.',
567
- 401,
568
- 'TOKEN_REUSE_DETECTED'
569
- );
570
- }
571
- tokenStore.markRefreshTokenUsed(payload.jti, payload.sub);
572
- }
573
-
574
- const user = await prisma.user.findUnique({
575
- where: { id: payload.sub },
576
- });
577
-
578
- if (!user) {
579
- throw new AuthError('User not found', 401, 'USER_NOT_FOUND');
580
- }
581
-
582
- return jwt.createTokenPair({
583
- id: user.id,
584
- email: user.email,
585
- roles: parseUserRoles(user.roles),
586
- });
587
- } catch (error) {
588
- if (error instanceof AuthError) throw error;
589
- throw new AuthError('Invalid refresh token', 401, 'INVALID_REFRESH_TOKEN');
590
- }
591
- }),
592
-
593
- logout: procedure()
594
- .rest({ method: 'POST', path: '/auth/logout' })
595
- .guard(authenticated)
596
- .output(LogoutResponse)
597
- .mutation(async ({ ctx }) => {
598
- const tokenId = ctx.auth?.token?.jti;
599
-
600
- if (tokenId) {
601
- tokenStore.revoke(tokenId, 15 * 60 * 1000);
602
- }
603
-
604
- return {
605
- success: true,
606
- message: 'Successfully logged out',
607
- };
608
- }),
609
-
610
- getMe: procedure()
611
- .rest({ method: 'GET', path: '/auth/me' })
612
- .guard(authenticated)
613
- .output(UserResponse)
614
- .query(async ({ ctx }) => {
615
- const user = ctx.user;
616
-
617
- if (!user) {
618
- throw new AuthError('Not authenticated', 401, 'NOT_AUTHENTICATED');
619
- }
620
-
621
- return {
622
- id: user.id,
623
- name: (user.name as string) || '',
624
- email: user.email,
625
- roles: Array.isArray(user.roles) ? user.roles : ['user'],
626
- };
627
- }),
628
- });
629
- `;
41
+ return compileTemplate('api/procedures/auth.ts', AUTH_CONFIG);
630
42
  }
631
- // ============================================================================
632
- // User Procedures (with guards)
633
- // ============================================================================
634
43
  function generateUserProceduresWithAuth() {
635
- return `/**
636
- * User Procedures
637
- *
638
- * CRUD procedures for user management with authentication guards.
639
- */
640
-
641
- import {
642
- AuthError,
643
- authenticated,
644
- hasRole,
645
- defineProcedures,
646
- GuardError,
647
- procedure,
648
- paginationInputSchema,
649
- z,
650
- } from '@veloxts/velox';
651
-
652
- import {
653
- CreateUserInput,
654
- UpdateUserInput,
655
- type User,
656
- UserSchema,
657
- } from '../schemas/user.js';
658
-
659
- // ============================================================================
660
- // Database Types
661
- // ============================================================================
662
-
663
- interface DbUser {
664
- id: string;
665
- name: string;
666
- email: string;
667
- createdAt: Date;
668
- updatedAt: Date;
669
- }
670
-
671
- interface DbClient {
672
- user: {
673
- findUnique: (args: { where: { id: string } }) => Promise<DbUser | null>;
674
- findMany: (args?: { skip?: number; take?: number }) => Promise<DbUser[]>;
675
- create: (args: { data: { name: string; email: string } }) => Promise<DbUser>;
676
- update: (args: { where: { id: string }; data: { name?: string; email?: string } }) => Promise<DbUser>;
677
- delete: (args: { where: { id: string } }) => Promise<DbUser>;
678
- count: () => Promise<number>;
679
- };
680
- }
681
-
682
- function getDb(ctx: { db: unknown }): DbClient {
683
- return ctx.db as DbClient;
684
- }
685
-
686
- function toUserResponse(dbUser: DbUser): User {
687
- return {
688
- id: dbUser.id,
689
- name: dbUser.name,
690
- email: dbUser.email,
691
- createdAt: dbUser.createdAt instanceof Date ? dbUser.createdAt.toISOString() : dbUser.createdAt,
692
- updatedAt: dbUser.updatedAt instanceof Date ? dbUser.updatedAt.toISOString() : dbUser.updatedAt,
693
- };
694
- }
695
-
696
- // ============================================================================
697
- // User Procedures
698
- // ============================================================================
699
-
700
- export const userProcedures = defineProcedures('users', {
701
- getUser: procedure()
702
- .input(z.object({ id: z.string().uuid() }))
703
- .output(UserSchema.nullable())
704
- .query(async ({ input, ctx }) => {
705
- const db = getDb(ctx);
706
- const user = await db.user.findUnique({ where: { id: input.id } });
707
- return user ? toUserResponse(user) : null;
708
- }),
709
-
710
- listUsers: procedure()
711
- .input(paginationInputSchema.optional())
712
- .output(
713
- z.object({
714
- data: z.array(UserSchema),
715
- meta: z.object({
716
- page: z.number(),
717
- limit: z.number(),
718
- total: z.number(),
719
- }),
720
- })
721
- )
722
- .query(async ({ input, ctx }) => {
723
- const db = getDb(ctx);
724
- const page = input?.page ?? 1;
725
- const limit = input?.limit ?? 10;
726
- const skip = (page - 1) * limit;
727
-
728
- const [dbUsers, total] = await Promise.all([
729
- db.user.findMany({ skip, take: limit }),
730
- db.user.count(),
731
- ]);
732
-
733
- return {
734
- data: dbUsers.map(toUserResponse),
735
- meta: { page, limit, total },
736
- };
737
- }),
738
-
739
- createUser: procedure()
740
- .guard(authenticated)
741
- .input(CreateUserInput)
742
- .output(UserSchema)
743
- .mutation(async ({ input, ctx }) => {
744
- const db = getDb(ctx);
745
- const user = await db.user.create({ data: input });
746
- return toUserResponse(user);
747
- }),
748
-
749
- updateUser: procedure()
750
- .guard(authenticated)
751
- .input(z.object({ id: z.string().uuid() }).merge(UpdateUserInput))
752
- .output(UserSchema)
753
- .mutation(async ({ input, ctx }) => {
754
- const db = getDb(ctx);
755
- const { id, ...data } = input;
756
-
757
- if (!ctx.user) {
758
- throw new AuthError('Authentication required', 401, 'NOT_AUTHENTICATED');
759
- }
760
-
761
- const isOwner = ctx.user.id === id;
762
- const isAdmin = Array.isArray(ctx.user.roles) && ctx.user.roles.includes('admin');
763
-
764
- if (!isOwner && !isAdmin) {
765
- throw new GuardError('ownership', 'You can only update your own profile', 403);
766
- }
767
-
768
- const updated = await db.user.update({ where: { id }, data });
769
- return toUserResponse(updated);
770
- }),
771
-
772
- patchUser: procedure()
773
- .guard(authenticated)
774
- .input(z.object({ id: z.string().uuid() }).merge(UpdateUserInput))
775
- .output(UserSchema)
776
- .mutation(async ({ input, ctx }) => {
777
- const db = getDb(ctx);
778
- const { id, ...data } = input;
779
-
780
- if (!ctx.user) {
781
- throw new AuthError('Authentication required', 401, 'NOT_AUTHENTICATED');
782
- }
783
-
784
- const isOwner = ctx.user.id === id;
785
- const isAdmin = Array.isArray(ctx.user.roles) && ctx.user.roles.includes('admin');
786
-
787
- if (!isOwner && !isAdmin) {
788
- throw new GuardError('ownership', 'You can only update your own profile', 403);
789
- }
790
-
791
- const updated = await db.user.update({ where: { id }, data });
792
- return toUserResponse(updated);
793
- }),
794
-
795
- deleteUser: procedure()
796
- .guard(hasRole('admin'))
797
- .input(z.object({ id: z.string().uuid() }))
798
- .output(z.object({ success: z.boolean() }))
799
- .mutation(async ({ input, ctx }) => {
800
- const db = getDb(ctx);
801
- await db.user.delete({ where: { id: input.id } });
802
- return { success: true };
803
- }),
804
- });
805
- `;
44
+ return compileTemplate('api/procedures/users.auth.ts', AUTH_CONFIG);
806
45
  }
807
- // ============================================================================
808
- // Entry Point (with auth plugin)
809
- // ============================================================================
810
46
  function generateIndexTs() {
811
- return `/**
812
- * Application Entry Point
813
- */
814
-
815
- import 'dotenv/config';
816
-
817
- import path from 'node:path';
818
-
819
- import fastifyStatic from '@fastify/static';
820
- import {
821
- veloxApp,
822
- VELOX_VERSION,
823
- databasePlugin,
824
- authPlugin,
825
- rest,
826
- getRouteSummary,
827
- } from '@veloxts/velox';
828
-
829
- import { authConfig, config } from './config/index.js';
830
- import { prisma } from './database/index.js';
831
- import { authProcedures, healthProcedures, userProcedures } from './procedures/index.js';
832
-
833
- // ============================================================================
834
- // Application Bootstrap
835
- // ============================================================================
836
-
837
- async function createApp() {
838
- const app = await veloxApp({
839
- port: config.port,
840
- host: config.host,
841
- logger: config.logger,
842
- });
843
-
844
- // Register database plugin
845
- await app.register(databasePlugin({ client: prisma }));
846
-
847
- // Register auth plugin
848
- await app.register(authPlugin(authConfig));
849
- console.log('[Auth] JWT authentication enabled');
850
-
851
- // Register static file serving
852
- await app.server.register(fastifyStatic, {
853
- root: path.join(process.cwd(), 'public'),
854
- prefix: '/',
855
- });
856
-
857
- // Register REST API routes
858
- const collections = [authProcedures, userProcedures, healthProcedures];
859
- app.routes(rest(collections, { prefix: config.apiPrefix }));
860
-
861
- return { app, collections };
862
- }
863
-
864
- function printBanner(collections: Parameters<typeof getRouteSummary>[0]) {
865
- const divider = '═'.repeat(50);
866
-
867
- console.log(\`\\n\${divider}\`);
868
- console.log(\` VeloxTS Application v\${VELOX_VERSION}\`);
869
- console.log(\` Environment: \${config.env}\`);
870
- console.log(divider);
871
-
872
- const routes = getRouteSummary(collections);
873
- console.log('\\n📍 Registered Routes:\\n');
874
-
875
- for (const route of routes) {
876
- const method = route.method.padEnd(6);
877
- const path = route.path.padEnd(25);
878
- console.log(\` \${method} \${path} → \${route.namespace}.\${route.procedure}\`);
879
- }
880
-
881
- console.log(\`\\n\${divider}\`);
882
- console.log(\` Frontend: http://localhost:\${config.port}\`);
883
- console.log(\` REST API: http://localhost:\${config.port}\${config.apiPrefix}\`);
884
- console.log(\`\${divider}\\n\`);
885
-
886
- console.log('📝 Example requests:\\n');
887
- console.log(' # Register');
888
- console.log(\` curl -X POST http://localhost:\${config.port}\${config.apiPrefix}/auth/register \\\\\`);
889
- console.log(' -H "Content-Type: application/json" \\\\');
890
- console.log(' -d \\'{"name":"John Doe","email":"john@example.com","password":"SecurePass123"}\\'');
891
- console.log('');
892
- console.log(' # Login');
893
- console.log(\` curl -X POST http://localhost:\${config.port}\${config.apiPrefix}/auth/login \\\\\`);
894
- console.log(' -H "Content-Type: application/json" \\\\');
895
- console.log(' -d \\'{"email":"john@example.com","password":"SecurePass123"}\\'');
896
- console.log('');
897
- console.log(' # Protected endpoint');
898
- console.log(\` curl http://localhost:\${config.port}\${config.apiPrefix}/auth/me \\\\\`);
899
- console.log(' -H "Authorization: Bearer <your-access-token>"');
900
- console.log('');
901
- }
902
-
903
- async function main() {
904
- try {
905
- const { app, collections } = await createApp();
906
- await app.start();
907
- printBanner(collections);
908
- } catch (error) {
909
- console.error('Failed to start application:', error);
910
- process.exit(1);
911
- }
912
- }
913
-
914
- main();
915
- `;
47
+ return compileTemplate('api/index.auth.ts', AUTH_CONFIG);
916
48
  }
917
- function generateDatabaseIndex() {
918
- return `/**
919
- * Database Client (Prisma 7.x)
920
- */
921
-
922
- import { PrismaBetterSqlite3 } from '@prisma/adapter-better-sqlite3';
923
-
924
- import { PrismaClient } from '../generated/prisma/client.js';
925
-
926
- if (!process.env.DATABASE_URL) {
927
- throw new Error('DATABASE_URL environment variable is required');
928
- }
929
-
930
- const adapter = new PrismaBetterSqlite3({ url: process.env.DATABASE_URL });
931
-
932
- export const prisma = new PrismaClient({ adapter });
933
- `;
49
+ function generateConfigDatabase() {
50
+ return compileTemplate('api/config/database.ts', AUTH_CONFIG);
934
51
  }
935
- function generateProceduresIndex() {
936
- return `/**
937
- * Procedure Exports
938
- */
939
-
940
- export * from './auth.js';
941
- export * from './health.js';
942
- export * from './users.js';
943
- `;
944
- }
945
- function generateSchemasIndex() {
946
- return `/**
947
- * Schema Exports
948
- */
949
-
950
- export * from './user.js';
951
- `;
52
+ function generateHealthProcedures() {
53
+ return compileTemplate('api/procedures/health.ts', AUTH_CONFIG);
952
54
  }
953
55
  function generateUserSchema() {
954
- return `/**
955
- * User Schemas
956
- */
957
-
958
- import { createIdSchema, emailSchema, z } from '@veloxts/velox';
959
-
960
- export const UserSchema = z.object({
961
- id: createIdSchema('uuid'),
962
- name: z.string().min(1).max(100),
963
- email: emailSchema,
964
- createdAt: z.coerce.date().transform((d) => d.toISOString()),
965
- updatedAt: z.coerce.date().transform((d) => d.toISOString()),
966
- });
967
-
968
- export type User = z.infer<typeof UserSchema>;
969
-
970
- export const CreateUserInput = z.object({
971
- name: z.string().min(1).max(100),
972
- email: emailSchema,
973
- });
974
-
975
- export type CreateUserData = z.infer<typeof CreateUserInput>;
976
-
977
- export const UpdateUserInput = z.object({
978
- name: z.string().min(1).max(100).optional(),
979
- email: emailSchema.optional(),
980
- });
981
-
982
- export type UpdateUserData = z.infer<typeof UpdateUserInput>;
983
- `;
984
- }
985
- // ============================================================================
986
- // CLAUDE.md for Auth
987
- // ============================================================================
988
- function generateClaudeMd(config) {
989
- return `# CLAUDE.md
990
-
991
- This file provides guidance to Claude Code and other AI assistants when working with this VeloxTS project.
992
-
993
- ## Project Overview
994
-
995
- **${config.projectName}** is a VeloxTS application with full JWT authentication.
996
-
997
- **Features:**
998
- - JWT authentication with access/refresh tokens
999
- - Rate limiting on auth endpoints
1000
- - Token rotation with reuse detection
1001
- - Role-based authorization guards
1002
- - Strong password requirements
1003
-
1004
- ## Commands
1005
-
1006
- \`\`\`bash
1007
- ${config.packageManager} dev # Start development server
1008
- ${config.packageManager} build # Build for production
1009
- ${config.packageManager} db:push # Push database schema
1010
- ${config.packageManager} db:studio # Open Prisma Studio
1011
- \`\`\`
1012
-
1013
- ## Authentication
1014
-
1015
- ### Endpoints
1016
-
1017
- | Endpoint | Method | Description |
1018
- |----------|--------|-------------|
1019
- | \`/auth/register\` | POST | Create new account |
1020
- | \`/auth/login\` | POST | Login and get tokens |
1021
- | \`/auth/refresh\` | POST | Refresh access token |
1022
- | \`/auth/logout\` | POST | Revoke current token |
1023
- | \`/auth/me\` | GET | Get current user (protected) |
1024
-
1025
- ### Usage
1026
-
1027
- 1. **Register/Login** to get tokens
1028
- 2. **Include token** in Authorization header: \`Bearer <accessToken>\`
1029
- 3. **Refresh token** when access token expires
1030
-
1031
- ### Security Features
1032
-
1033
- - **Rate Limiting**: Login 5/15min, Register 3/hour
1034
- - **Token Rotation**: Refresh tokens are single-use
1035
- - **Reuse Detection**: Token reuse triggers security alert
1036
- - **Password Policy**: 12+ chars, uppercase, lowercase, number
1037
-
1038
- ## Guards
1039
-
1040
- \`\`\`typescript
1041
- // Require authentication
1042
- procedure().guard(authenticated)
1043
-
1044
- // Require specific role
1045
- procedure().guard(hasRole('admin'))
1046
-
1047
- // Custom owner-or-admin check
1048
- if (!isOwner && !isAdmin) {
1049
- throw new GuardError('ownership', 'Access denied', 403);
1050
- }
1051
- \`\`\`
1052
-
1053
- ## Alternative: Session-Based Authentication
1054
-
1055
- VeloxTS also supports cookie-based session authentication as an alternative to JWT.
1056
- Session auth is useful for:
1057
- - Traditional web applications with server-side rendering
1058
- - Applications where token storage in the browser is a concern
1059
- - Simple authentication flows without refresh token management
1060
-
1061
- \`\`\`typescript
1062
- import { sessionMiddleware, loginSession, logoutSession } from '@veloxts/velox';
1063
-
1064
- // Create session middleware
1065
- const session = sessionMiddleware({
1066
- secret: process.env.SESSION_SECRET!,
1067
- cookie: { secure: true, httpOnly: true, sameSite: 'lax' },
1068
- expiration: { ttl: 86400, sliding: true },
1069
- userLoader: async (userId) => prisma.user.findUnique({ where: { id: userId } }),
1070
- });
1071
-
1072
- // Use in procedures
1073
- const getProfile = procedure()
1074
- .use(session.requireAuth())
1075
- .query(async ({ ctx }) => ctx.user);
1076
-
1077
- // Login helper
1078
- await loginSession(ctx.session, user); // Regenerates session ID
1079
-
1080
- // Logout helper
1081
- await logoutSession(ctx.session); // Destroys session
1082
- \`\`\`
1083
-
1084
- ## Environment Variables
1085
-
1086
- \`\`\`bash
1087
- # Required for JWT authentication
1088
- JWT_SECRET=<64+ chars>
1089
- JWT_REFRESH_SECRET=<64+ chars>
1090
-
1091
- # Generate with: openssl rand -base64 64
1092
-
1093
- # Required for session-based authentication (alternative to JWT)
1094
- SESSION_SECRET=<32+ chars>
1095
-
1096
- # Generate with: openssl rand -base64 32
1097
- \`\`\`
1098
-
1099
- ## Project Structure
1100
-
1101
- \`\`\`
1102
- src/
1103
- ├── config/
1104
- │ ├── app.ts # App configuration
1105
- │ └── auth.ts # Auth configuration + token store
1106
- ├── procedures/
1107
- │ ├── auth.ts # Auth endpoints
1108
- │ ├── users.ts # User CRUD with guards
1109
- │ └── health.ts # Health check
1110
- ├── schemas/
1111
- │ └── user.ts # Zod schemas
1112
- └── index.ts # Entry point
1113
- \`\`\`
1114
- `;
56
+ return compileTemplate('api/schemas/user.ts', AUTH_CONFIG);
1115
57
  }
1116
58
  // ============================================================================
1117
59
  // Auth Template Generator
1118
60
  // ============================================================================
1119
61
  export function generateAuthTemplate(config) {
1120
62
  const files = [
1121
- // Root files
1122
- { path: 'package.json', content: generatePackageJson(config) },
1123
- { path: '.env.example', content: generateEnvExample() },
1124
- { path: '.env', content: generateEnvExample() },
1125
- { path: 'CLAUDE.md', content: generateClaudeMd(config) },
63
+ // API package files
64
+ { path: 'apps/api/package.json', content: generateApiPackageJson(config) },
65
+ { path: 'apps/api/tsconfig.json', content: generateApiTsConfig() },
66
+ { path: 'apps/api/tsup.config.ts', content: generateApiTsupConfig() },
67
+ { path: 'apps/api/prisma.config.ts', content: generatePrismaConfig() },
68
+ { path: 'apps/api/.env.example', content: generateEnvExample(config) },
69
+ { path: 'apps/api/.env', content: generateEnvExample(config) },
1126
70
  // Prisma
1127
- { path: 'prisma/schema.prisma', content: generatePrismaSchema() },
1128
- // Source files
1129
- { path: 'src/index.ts', content: generateIndexTs() },
1130
- { path: 'src/config/index.ts', content: generateConfigIndexWithAuth() },
1131
- { path: 'src/config/auth.ts', content: generateAuthConfig() },
1132
- { path: 'src/database/index.ts', content: generateDatabaseIndex() },
1133
- { path: 'src/procedures/index.ts', content: generateProceduresIndex() },
1134
- { path: 'src/procedures/auth.ts', content: generateAuthProcedures() },
1135
- { path: 'src/procedures/users.ts', content: generateUserProceduresWithAuth() },
1136
- { path: 'src/schemas/index.ts', content: generateSchemasIndex() },
1137
- { path: 'src/schemas/user.ts', content: generateUserSchema() },
71
+ { path: 'apps/api/prisma/schema.prisma', content: generatePrismaSchema() },
72
+ // API Source files
73
+ { path: 'apps/api/src/index.ts', content: generateIndexTs() },
74
+ { path: 'apps/api/src/config/app.ts', content: generateConfigApp(config) },
75
+ { path: 'apps/api/src/config/auth.ts', content: generateAuthConfig() },
76
+ { path: 'apps/api/src/config/database.ts', content: generateConfigDatabase() },
77
+ { path: 'apps/api/src/procedures/health.ts', content: generateHealthProcedures() },
78
+ { path: 'apps/api/src/procedures/auth.ts', content: generateAuthProcedures() },
79
+ { path: 'apps/api/src/procedures/users.ts', content: generateUserProceduresWithAuth() },
80
+ { path: 'apps/api/src/schemas/user.ts', content: generateUserSchema() },
1138
81
  ];
1139
- // Add shared files, but filter out config/index.ts since auth template has its own
1140
- const sharedFiles = generateSharedFiles(config).filter((file) => file.path !== 'src/config/index.ts');
1141
- return [...files, ...sharedFiles];
82
+ // Add root workspace files
83
+ const rootFiles = generateRootFiles(config, true);
84
+ // Add web package files (with auth UI)
85
+ const webBaseFiles = generateWebBaseFiles(config, true);
86
+ const webStyleFiles = generateWebStyleFiles();
87
+ return [...files, ...rootFiles, ...webBaseFiles, ...webStyleFiles];
1142
88
  }
1143
89
  //# sourceMappingURL=auth.js.map