@factiii/auth 0.1.0
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.
- package/LICENSE +21 -0
- package/README.md +120 -0
- package/bin/init.mjs +315 -0
- package/dist/chunk-CLHDX2R2.mjs +118 -0
- package/dist/chunk-CLHDX2R2.mjs.map +1 -0
- package/dist/hooks-B4Kl294A.d.mts +400 -0
- package/dist/hooks-B4Kl294A.d.ts +400 -0
- package/dist/index.d.mts +1061 -0
- package/dist/index.d.ts +1061 -0
- package/dist/index.js +2096 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1947 -0
- package/dist/index.mjs.map +1 -0
- package/dist/validators.d.mts +2 -0
- package/dist/validators.d.ts +2 -0
- package/dist/validators.js +164 -0
- package/dist/validators.js.map +1 -0
- package/dist/validators.mjs +51 -0
- package/dist/validators.mjs.map +1 -0
- package/package.json +104 -0
- package/prisma/schema.prisma +138 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Factiii
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# @factiii/auth
|
|
2
|
+
|
|
3
|
+
Drop-in authentication for tRPC. JWT sessions, OAuth, 2FA—all type-safe.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @factiii/auth @prisma/client
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Setup
|
|
12
|
+
|
|
13
|
+
**1. Add Prisma models:**
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npx @factiii/auth init
|
|
17
|
+
npx prisma generate && npx prisma db push
|
|
18
|
+
npx @factiii/auth doctor # Verify setup
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
**2. Create auth router:**
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
import { createAuthRouter } from '@factiii/auth';
|
|
25
|
+
import { prisma } from './prisma';
|
|
26
|
+
|
|
27
|
+
export const { router, authProcedure, createContext } = createAuthRouter({
|
|
28
|
+
prisma,
|
|
29
|
+
secrets: { jwt: process.env.JWT_SECRET! },
|
|
30
|
+
});
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**3. Use protected routes:**
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
const protectedRouter = router({
|
|
37
|
+
getProfile: authProcedure.query(({ ctx }) => {
|
|
38
|
+
return { userId: ctx.userId };
|
|
39
|
+
}),
|
|
40
|
+
});
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Config
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
createAuthRouter({
|
|
47
|
+
prisma,
|
|
48
|
+
secrets: { jwt: 'your-secret' },
|
|
49
|
+
|
|
50
|
+
// Optional
|
|
51
|
+
features: {
|
|
52
|
+
emailVerification: true,
|
|
53
|
+
twoFa: true,
|
|
54
|
+
oauth: { google: true, apple: true },
|
|
55
|
+
biometric: false,
|
|
56
|
+
},
|
|
57
|
+
oauthKeys: {
|
|
58
|
+
google: { clientId: '...' },
|
|
59
|
+
apple: { clientId: '...' },
|
|
60
|
+
},
|
|
61
|
+
emailService: {
|
|
62
|
+
sendVerificationEmail: async (email, code) => {},
|
|
63
|
+
sendPasswordResetEmail: async (email, token) => {},
|
|
64
|
+
sendOTPEmail: async (email, otp) => {},
|
|
65
|
+
},
|
|
66
|
+
hooks: {
|
|
67
|
+
onUserCreated: async (userId) => {},
|
|
68
|
+
onUserLogin: async (userId, sessionId) => {},
|
|
69
|
+
// ... 15+ lifecycle hooks
|
|
70
|
+
},
|
|
71
|
+
tokenSettings: {
|
|
72
|
+
accessTokenExpiry: '5m', // JWT expiry (default: 5 minutes)
|
|
73
|
+
passwordResetExpiryMs: 3600000, // Reset token expiry (default: 1 hour)
|
|
74
|
+
otpValidityMs: 900000, // OTP validity window (default: 15 minutes)
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Procedures
|
|
80
|
+
|
|
81
|
+
Auth procedures: `register`, `login`, `logout`, `refresh`, `changePassword`, `resetPassword`, `oAuthLogin`, `enableTwofa`, `disableTwofa`, `sendVerificationEmail`, `verifyEmail`, and more.
|
|
82
|
+
|
|
83
|
+
## Lifecycle Hooks
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
interface AuthHooks {
|
|
87
|
+
// Registration & Login
|
|
88
|
+
beforeRegister?: (input) => Promise<void>;
|
|
89
|
+
beforeLogin?: (input) => Promise<void>;
|
|
90
|
+
onUserCreated?: (userId, input) => Promise<void>;
|
|
91
|
+
onUserLogin?: (userId, sessionId) => Promise<void>;
|
|
92
|
+
|
|
93
|
+
// Sessions
|
|
94
|
+
onSessionCreated?: (sessionId) => Promise<void>;
|
|
95
|
+
onSessionRevoked?: (sessionId, socketId, reason) => Promise<void>;
|
|
96
|
+
afterLogout?: (userId, sessionId, socketId) => Promise<void>;
|
|
97
|
+
onRefresh?: (userId) => Promise<void>;
|
|
98
|
+
|
|
99
|
+
// Security
|
|
100
|
+
onPasswordChanged?: (userId) => Promise<void>;
|
|
101
|
+
onEmailVerified?: (userId) => Promise<void>;
|
|
102
|
+
onTwoFaStatusChanged?: (userId, enabled) => Promise<void>;
|
|
103
|
+
onOAuthLinked?: (userId, provider) => Promise<void>;
|
|
104
|
+
onBiometricVerified?: (userId) => Promise<void>;
|
|
105
|
+
getBiometricTimeout?: () => Promise<number | null>;
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## CLI
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
npx @factiii/auth init # Copy Prisma schema to your project
|
|
113
|
+
npx @factiii/auth schema # Print schema path for manual copying
|
|
114
|
+
npx @factiii/auth doctor # Check setup for common issues
|
|
115
|
+
npx @factiii/auth help # Show help
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## License
|
|
119
|
+
|
|
120
|
+
MIT
|
package/bin/init.mjs
ADDED
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync } from 'node:fs';
|
|
4
|
+
import { dirname, join, resolve } from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
|
|
7
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const schemaSource = join(__dirname, '..', 'prisma', 'schema.prisma');
|
|
9
|
+
const targetDir = resolve(process.cwd(), 'prisma');
|
|
10
|
+
const targetFile = join(targetDir, 'auth-schema.prisma');
|
|
11
|
+
|
|
12
|
+
const args = process.argv.slice(2);
|
|
13
|
+
const command = args[0];
|
|
14
|
+
|
|
15
|
+
function printHelp() {
|
|
16
|
+
console.log(`
|
|
17
|
+
@factiii/auth CLI
|
|
18
|
+
|
|
19
|
+
Usage:
|
|
20
|
+
npx @factiii/auth <command>
|
|
21
|
+
|
|
22
|
+
Commands:
|
|
23
|
+
init Copy the reference Prisma schema to your project
|
|
24
|
+
schema Print the schema path (for manual copying)
|
|
25
|
+
doctor Check your project setup for common issues
|
|
26
|
+
help Show this help message
|
|
27
|
+
|
|
28
|
+
Examples:
|
|
29
|
+
npx @factiii/auth init
|
|
30
|
+
npx @factiii/auth doctor
|
|
31
|
+
`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function init() {
|
|
35
|
+
// Ensure prisma directory exists
|
|
36
|
+
if (!existsSync(targetDir)) {
|
|
37
|
+
mkdirSync(targetDir, { recursive: true });
|
|
38
|
+
console.log('Created prisma/ directory');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Check if file already exists
|
|
42
|
+
if (existsSync(targetFile)) {
|
|
43
|
+
console.log(`⚠️ ${targetFile} already exists`);
|
|
44
|
+
console.log(' To overwrite, delete it first and run again.');
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Copy schema
|
|
49
|
+
try {
|
|
50
|
+
copyFileSync(schemaSource, targetFile);
|
|
51
|
+
console.log(`✓ Copied schema to ${targetFile}`);
|
|
52
|
+
console.log('');
|
|
53
|
+
console.log('Next steps:');
|
|
54
|
+
console.log(' 1. Review and customize the schema for your database provider');
|
|
55
|
+
console.log(' 2. Merge models into your existing schema.prisma (if you have one)');
|
|
56
|
+
console.log(' 3. Run: npx prisma generate');
|
|
57
|
+
console.log(' 4. Run: npx prisma db push (or prisma migrate dev)');
|
|
58
|
+
} catch (err) {
|
|
59
|
+
console.error('Failed to copy schema:', err.message);
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function printSchemaPath() {
|
|
65
|
+
console.log('Schema location:');
|
|
66
|
+
console.log(` ${schemaSource}`);
|
|
67
|
+
console.log('');
|
|
68
|
+
console.log('Copy manually:');
|
|
69
|
+
console.log(` cp "${schemaSource}" ./prisma/auth-schema.prisma`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ANSI color codes
|
|
73
|
+
const colors = {
|
|
74
|
+
reset: '\x1b[0m',
|
|
75
|
+
red: '\x1b[31m',
|
|
76
|
+
green: '\x1b[32m',
|
|
77
|
+
yellow: '\x1b[33m',
|
|
78
|
+
cyan: '\x1b[36m',
|
|
79
|
+
bold: '\x1b[1m',
|
|
80
|
+
dim: '\x1b[2m'
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const ok = (msg) => console.log(`${colors.green}✓${colors.reset} ${msg}`);
|
|
84
|
+
const fail = (msg) => console.log(`${colors.red}✗${colors.reset} ${msg}`);
|
|
85
|
+
const warn = (msg) => console.log(`${colors.yellow}⚠${colors.reset} ${msg}`);
|
|
86
|
+
const hint = (msg) => console.log(`${colors.dim} ${msg}${colors.reset}`);
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Recursively find all .prisma files in a directory
|
|
90
|
+
* @param {string} dir - Directory to search
|
|
91
|
+
* @param {string[]} files - Accumulator for found files
|
|
92
|
+
* @returns {string[]} Array of file paths
|
|
93
|
+
*/
|
|
94
|
+
function findPrismaFiles(dir, files = []) {
|
|
95
|
+
if (!existsSync(dir)) return files;
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
99
|
+
for (const entry of entries) {
|
|
100
|
+
const fullPath = join(dir, entry.name);
|
|
101
|
+
if (entry.isDirectory() && entry.name !== 'migrations') {
|
|
102
|
+
findPrismaFiles(fullPath, files);
|
|
103
|
+
} else if (entry.isFile() && entry.name.endsWith('.prisma')) {
|
|
104
|
+
files.push(fullPath);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
} catch (e) {
|
|
108
|
+
// Directory read failed
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return files;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Find and read all Prisma schema files (supports single file and modularized schemas)
|
|
116
|
+
* Patterns supported:
|
|
117
|
+
* - prisma/schema.prisma (single file)
|
|
118
|
+
* - prisma/schema/*.prisma (modularized in schema dir)
|
|
119
|
+
* - prisma/schema.prisma + prisma/models/*.prisma (hybrid)
|
|
120
|
+
* - prisma/*.prisma (multiple files in prisma dir)
|
|
121
|
+
* @returns {{ found: boolean, schemaContent: string, location: string }}
|
|
122
|
+
*/
|
|
123
|
+
function findPrismaSchema() {
|
|
124
|
+
const prismaDir = resolve(process.cwd(), 'prisma');
|
|
125
|
+
|
|
126
|
+
if (!existsSync(prismaDir)) {
|
|
127
|
+
return { found: false, schemaContent: '', location: '' };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Find all .prisma files recursively (excluding migrations)
|
|
131
|
+
const allFiles = findPrismaFiles(prismaDir);
|
|
132
|
+
|
|
133
|
+
if (allFiles.length === 0) {
|
|
134
|
+
return { found: false, schemaContent: '', location: '' };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Read and combine all schema files
|
|
138
|
+
const combinedSchema = allFiles
|
|
139
|
+
.map(f => readFileSync(f, 'utf-8'))
|
|
140
|
+
.join('\n');
|
|
141
|
+
|
|
142
|
+
// Build location description
|
|
143
|
+
let location;
|
|
144
|
+
if (allFiles.length === 1 && allFiles[0] === join(prismaDir, 'schema.prisma')) {
|
|
145
|
+
location = 'prisma/schema.prisma';
|
|
146
|
+
} else {
|
|
147
|
+
// Get unique directories
|
|
148
|
+
const dirs = [...new Set(allFiles.map(f => dirname(f).replace(prismaDir, 'prisma')))];
|
|
149
|
+
location = `${dirs.join(', ')} (${allFiles.length} files)`;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
found: true,
|
|
154
|
+
schemaContent: combinedSchema,
|
|
155
|
+
location
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function parseReferenceSchema() {
|
|
160
|
+
const schema = readFileSync(schemaSource, 'utf-8');
|
|
161
|
+
|
|
162
|
+
// Extract model names
|
|
163
|
+
const models = [...schema.matchAll(/model\s+(\w+)\s*\{/g)].map(m => m[1]);
|
|
164
|
+
|
|
165
|
+
// Extract enum names
|
|
166
|
+
const enums = [...schema.matchAll(/enum\s+(\w+)\s*\{/g)].map(m => m[1]);
|
|
167
|
+
|
|
168
|
+
// Extract fields for each model
|
|
169
|
+
const modelFields = {};
|
|
170
|
+
for (const model of models) {
|
|
171
|
+
const modelMatch = schema.match(new RegExp(`model\\s+${model}\\s*\\{([^}]+)\\}`, 'm'));
|
|
172
|
+
if (modelMatch) {
|
|
173
|
+
const block = modelMatch[1];
|
|
174
|
+
// Match field names (first word on lines that aren't comments or @@)
|
|
175
|
+
const fields = [...block.matchAll(/^\s*(\w+)\s+\w+/gm)]
|
|
176
|
+
.map(m => m[1])
|
|
177
|
+
.filter(f => !f.startsWith('@@'));
|
|
178
|
+
modelFields[model] = fields;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return { models, enums, modelFields };
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function doctor() {
|
|
186
|
+
console.log(`${colors.bold}${colors.cyan}Running diagnostics...${colors.reset}\n`);
|
|
187
|
+
|
|
188
|
+
let issues = 0;
|
|
189
|
+
let warnings = 0;
|
|
190
|
+
|
|
191
|
+
// Parse reference schema to get required models/enums/fields
|
|
192
|
+
let reference;
|
|
193
|
+
try {
|
|
194
|
+
reference = parseReferenceSchema();
|
|
195
|
+
} catch (e) {
|
|
196
|
+
fail('Could not read reference schema from package');
|
|
197
|
+
process.exit(1);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Check 1: package.json exists
|
|
201
|
+
const packageJsonPath = resolve(process.cwd(), 'package.json');
|
|
202
|
+
if (!existsSync(packageJsonPath)) {
|
|
203
|
+
fail('No package.json found in current directory');
|
|
204
|
+
issues++;
|
|
205
|
+
} else {
|
|
206
|
+
ok('package.json found');
|
|
207
|
+
|
|
208
|
+
// Check for @prisma/client dependency
|
|
209
|
+
try {
|
|
210
|
+
const pkg = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
211
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
212
|
+
if (deps['@prisma/client']) {
|
|
213
|
+
ok('@prisma/client is installed');
|
|
214
|
+
} else {
|
|
215
|
+
fail('@prisma/client not found in dependencies');
|
|
216
|
+
hint('Run: npm install @prisma/client');
|
|
217
|
+
issues++;
|
|
218
|
+
}
|
|
219
|
+
} catch (e) {
|
|
220
|
+
warn('Could not parse package.json');
|
|
221
|
+
warnings++;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Check 2: Prisma schema exists (supports single file and modularized schemas)
|
|
226
|
+
const { found: schemaFound, schemaContent: schema, location: schemaLocation } = findPrismaSchema();
|
|
227
|
+
if (!schemaFound) {
|
|
228
|
+
fail('No Prisma schema found');
|
|
229
|
+
hint('Checked: prisma/schema.prisma, prisma/schema/*.prisma, prisma/*.prisma');
|
|
230
|
+
hint('Run: npx @factiii/auth init');
|
|
231
|
+
issues++;
|
|
232
|
+
} else {
|
|
233
|
+
ok(`Prisma schema found (${schemaLocation})`);
|
|
234
|
+
|
|
235
|
+
// Parse user's schema
|
|
236
|
+
try {
|
|
237
|
+
// Check models
|
|
238
|
+
for (const model of reference.models) {
|
|
239
|
+
const regex = new RegExp(`model\\s+${model}\\s*\\{`, 'm');
|
|
240
|
+
if (regex.test(schema)) {
|
|
241
|
+
ok(`Model ${colors.cyan}${model}${colors.reset} found`);
|
|
242
|
+
} else {
|
|
243
|
+
fail(`Model ${colors.cyan}${model}${colors.reset} not found in schema`);
|
|
244
|
+
issues++;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Check enums
|
|
249
|
+
for (const enumName of reference.enums) {
|
|
250
|
+
const regex = new RegExp(`enum\\s+${enumName}\\s*\\{`, 'm');
|
|
251
|
+
if (regex.test(schema)) {
|
|
252
|
+
ok(`Enum ${colors.cyan}${enumName}${colors.reset} found`);
|
|
253
|
+
} else {
|
|
254
|
+
fail(`Enum ${colors.cyan}${enumName}${colors.reset} not found in schema`);
|
|
255
|
+
issues++;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Check fields for each model
|
|
260
|
+
for (const [model, fields] of Object.entries(reference.modelFields)) {
|
|
261
|
+
const modelMatch = schema.match(new RegExp(`model\\s+${model}\\s*\\{([^}]+)\\}`, 'm'));
|
|
262
|
+
if (modelMatch) {
|
|
263
|
+
const block = modelMatch[1];
|
|
264
|
+
for (const field of fields) {
|
|
265
|
+
const fieldRegex = new RegExp(`\\b${field}\\b`, 'm');
|
|
266
|
+
if (!fieldRegex.test(block)) {
|
|
267
|
+
warn(`${model}.${colors.cyan}${field}${colors.reset} field not found`);
|
|
268
|
+
warnings++;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
} catch (e) {
|
|
274
|
+
warn('Could not parse Prisma schema');
|
|
275
|
+
warnings++;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Summary
|
|
280
|
+
console.log(`\n${colors.bold}--- Summary ---${colors.reset}`);
|
|
281
|
+
if (issues === 0 && warnings === 0) {
|
|
282
|
+
console.log(`${colors.green}${colors.bold}✓ All checks passed!${colors.reset} Your setup looks good.`);
|
|
283
|
+
} else {
|
|
284
|
+
if (issues > 0) {
|
|
285
|
+
console.log(`${colors.red}${colors.bold}✗ ${issues} issue(s) found${colors.reset}`);
|
|
286
|
+
}
|
|
287
|
+
if (warnings > 0) {
|
|
288
|
+
console.log(`${colors.yellow}${colors.bold}⚠ ${warnings} warning(s) found${colors.reset}`);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
process.exit(issues > 0 ? 1 : 0);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
switch (command) {
|
|
296
|
+
case 'init':
|
|
297
|
+
init();
|
|
298
|
+
break;
|
|
299
|
+
case 'schema':
|
|
300
|
+
printSchemaPath();
|
|
301
|
+
break;
|
|
302
|
+
case 'doctor':
|
|
303
|
+
doctor();
|
|
304
|
+
break;
|
|
305
|
+
case 'help':
|
|
306
|
+
case '--help':
|
|
307
|
+
case '-h':
|
|
308
|
+
case undefined:
|
|
309
|
+
printHelp();
|
|
310
|
+
break;
|
|
311
|
+
default:
|
|
312
|
+
console.error(`Unknown command: ${command}`);
|
|
313
|
+
printHelp();
|
|
314
|
+
process.exit(1);
|
|
315
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// src/validators.ts
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
var usernameValidationRegex = /^[a-zA-Z0-9_]+$/;
|
|
4
|
+
var signupSchema = z.object({
|
|
5
|
+
username: z.string().min(1, { message: "Username is required" }).max(30, { message: "Username must be 30 characters or less" }).regex(usernameValidationRegex, {
|
|
6
|
+
message: "Username can only contain letters, numbers, and underscores"
|
|
7
|
+
}),
|
|
8
|
+
email: z.string().email({ message: "Invalid email address" }),
|
|
9
|
+
password: z.string().min(6, { message: "Password must contain at least 6 characters" })
|
|
10
|
+
});
|
|
11
|
+
var loginSchema = z.object({
|
|
12
|
+
username: z.string().min(1, { message: "Username or email is required" }),
|
|
13
|
+
password: z.string().min(1, { message: "Password is required" }),
|
|
14
|
+
code: z.string().optional()
|
|
15
|
+
// 2FA code
|
|
16
|
+
});
|
|
17
|
+
var oAuthLoginSchema = z.object({
|
|
18
|
+
idToken: z.string(),
|
|
19
|
+
user: z.object({
|
|
20
|
+
email: z.string().email().optional()
|
|
21
|
+
}).optional(),
|
|
22
|
+
provider: z.enum(["GOOGLE", "APPLE"])
|
|
23
|
+
});
|
|
24
|
+
var requestPasswordResetSchema = z.object({
|
|
25
|
+
email: z.string().email({ message: "Invalid email address" })
|
|
26
|
+
});
|
|
27
|
+
var resetPasswordSchema = z.object({
|
|
28
|
+
token: z.string().min(1, { message: "Reset token is required" }),
|
|
29
|
+
password: z.string().min(6, { message: "Password must contain at least 6 characters" })
|
|
30
|
+
});
|
|
31
|
+
var checkPasswordResetSchema = z.object({
|
|
32
|
+
token: z.string().min(1, { message: "Reset token is required" })
|
|
33
|
+
});
|
|
34
|
+
var changePasswordSchema = z.object({
|
|
35
|
+
currentPassword: z.string().min(1, { message: "Current password is required" }),
|
|
36
|
+
newPassword: z.string().min(6, { message: "New password must contain at least 6 characters" })
|
|
37
|
+
});
|
|
38
|
+
var twoFaVerifySchema = z.object({
|
|
39
|
+
code: z.string().min(6, { message: "Verification code is required" }),
|
|
40
|
+
sessionId: z.number().optional()
|
|
41
|
+
});
|
|
42
|
+
var twoFaSetupSchema = z.object({
|
|
43
|
+
code: z.string().min(6, { message: "Verification code is required" })
|
|
44
|
+
});
|
|
45
|
+
var twoFaResetSchema = z.object({
|
|
46
|
+
username: z.string().min(1),
|
|
47
|
+
password: z.string().min(1)
|
|
48
|
+
});
|
|
49
|
+
var twoFaResetVerifySchema = z.object({
|
|
50
|
+
code: z.number().min(1e5).max(999999),
|
|
51
|
+
username: z.string().min(1)
|
|
52
|
+
});
|
|
53
|
+
var verifyEmailSchema = z.object({
|
|
54
|
+
code: z.string().min(1, { message: "Verification code is required" })
|
|
55
|
+
});
|
|
56
|
+
var resendVerificationSchema = z.object({
|
|
57
|
+
email: z.string().email().optional()
|
|
58
|
+
});
|
|
59
|
+
var biometricVerifySchema = z.object({});
|
|
60
|
+
var registerPushTokenSchema = z.object({
|
|
61
|
+
pushToken: z.string().min(1, { message: "Push token is required" })
|
|
62
|
+
});
|
|
63
|
+
var deregisterPushTokenSchema = z.object({
|
|
64
|
+
pushToken: z.string().min(1, { message: "Push token is required" })
|
|
65
|
+
});
|
|
66
|
+
var getTwofaSecretSchema = z.object({
|
|
67
|
+
pushCode: z.string().min(6, { message: "Push code is required" })
|
|
68
|
+
});
|
|
69
|
+
var disableTwofaSchema = z.object({
|
|
70
|
+
password: z.string().min(1, { message: "Password is required" })
|
|
71
|
+
});
|
|
72
|
+
var logoutSchema = z.object({
|
|
73
|
+
allDevices: z.boolean().optional().default(false)
|
|
74
|
+
});
|
|
75
|
+
var endAllSessionsSchema = z.object({
|
|
76
|
+
skipCurrentSession: z.boolean().optional().default(true)
|
|
77
|
+
});
|
|
78
|
+
var otpLoginRequestSchema = z.object({
|
|
79
|
+
email: z.string().email({ message: "Invalid email address" })
|
|
80
|
+
});
|
|
81
|
+
var otpLoginVerifySchema = z.object({
|
|
82
|
+
email: z.string().email(),
|
|
83
|
+
code: z.number().min(1e5).max(999999)
|
|
84
|
+
});
|
|
85
|
+
function createSchemas(extensions) {
|
|
86
|
+
return {
|
|
87
|
+
signup: extensions?.signup ? signupSchema.merge(extensions.signup) : signupSchema,
|
|
88
|
+
login: extensions?.login ? loginSchema.merge(extensions.login) : loginSchema,
|
|
89
|
+
oauth: extensions?.oauth ? oAuthLoginSchema.merge(extensions.oauth) : oAuthLoginSchema
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export {
|
|
94
|
+
signupSchema,
|
|
95
|
+
loginSchema,
|
|
96
|
+
oAuthLoginSchema,
|
|
97
|
+
requestPasswordResetSchema,
|
|
98
|
+
resetPasswordSchema,
|
|
99
|
+
checkPasswordResetSchema,
|
|
100
|
+
changePasswordSchema,
|
|
101
|
+
twoFaVerifySchema,
|
|
102
|
+
twoFaSetupSchema,
|
|
103
|
+
twoFaResetSchema,
|
|
104
|
+
twoFaResetVerifySchema,
|
|
105
|
+
verifyEmailSchema,
|
|
106
|
+
resendVerificationSchema,
|
|
107
|
+
biometricVerifySchema,
|
|
108
|
+
registerPushTokenSchema,
|
|
109
|
+
deregisterPushTokenSchema,
|
|
110
|
+
getTwofaSecretSchema,
|
|
111
|
+
disableTwofaSchema,
|
|
112
|
+
logoutSchema,
|
|
113
|
+
endAllSessionsSchema,
|
|
114
|
+
otpLoginRequestSchema,
|
|
115
|
+
otpLoginVerifySchema,
|
|
116
|
+
createSchemas
|
|
117
|
+
};
|
|
118
|
+
//# sourceMappingURL=chunk-CLHDX2R2.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/validators.ts"],"sourcesContent":["import { z, type AnyZodObject } from 'zod';\n\nimport type { SchemaExtensions } from './types/hooks';\n\n/**\n * Username validation regex - allows letters, numbers, and underscores\n */\nconst usernameValidationRegex = /^[a-zA-Z0-9_]+$/;\n\n/**\n * Schema for user registration\n */\nexport const signupSchema = z.object({\n username: z\n .string()\n .min(1, { message: 'Username is required' })\n .max(30, { message: 'Username must be 30 characters or less' })\n .regex(usernameValidationRegex, {\n message: 'Username can only contain letters, numbers, and underscores'\n }),\n email: z.string().email({ message: 'Invalid email address' }),\n password: z\n .string()\n .min(6, { message: 'Password must contain at least 6 characters' })\n});\n\n/**\n * Schema for user login\n */\nexport const loginSchema = z.object({\n username: z.string().min(1, { message: 'Username or email is required' }),\n password: z.string().min(1, { message: 'Password is required' }),\n code: z.string().optional() // 2FA code\n});\n\n/**\n * Schema for OAuth login\n */\nexport const oAuthLoginSchema = z.object({\n idToken: z.string(),\n user: z\n .object({\n email: z.string().email().optional()\n })\n .optional(),\n provider: z.enum(['GOOGLE', 'APPLE'])\n});\n\n/**\n * Schema for password reset request\n */\nexport const requestPasswordResetSchema = z.object({\n email: z.string().email({ message: 'Invalid email address' })\n});\n\n/**\n * Schema for password reset confirmation\n */\nexport const resetPasswordSchema = z.object({\n token: z.string().min(1, { message: 'Reset token is required' }),\n password: z\n .string()\n .min(6, { message: 'Password must contain at least 6 characters' })\n});\n\n/**\n * Schema for checking password reset token\n */\nexport const checkPasswordResetSchema = z.object({\n token: z.string().min(1, { message: 'Reset token is required' })\n});\n\n/**\n * Schema for changing password (authenticated)\n */\nexport const changePasswordSchema = z.object({\n currentPassword: z\n .string()\n .min(1, { message: 'Current password is required' }),\n newPassword: z\n .string()\n .min(6, { message: 'New password must contain at least 6 characters' })\n});\n\n/**\n * Schema for 2FA verification\n */\nexport const twoFaVerifySchema = z.object({\n code: z.string().min(6, { message: 'Verification code is required' }),\n sessionId: z.number().optional()\n});\n\n/**\n * Schema for 2FA setup\n */\nexport const twoFaSetupSchema = z.object({\n code: z.string().min(6, { message: 'Verification code is required' })\n});\n\n/**\n * Schema for 2FA reset request\n */\nexport const twoFaResetSchema = z.object({\n username: z.string().min(1),\n password: z.string().min(1)\n});\n\n/**\n * Schema for 2FA reset verification\n */\nexport const twoFaResetVerifySchema = z.object({\n code: z.number().min(100000).max(999999),\n username: z.string().min(1)\n});\n\n/**\n * Schema for email verification\n */\nexport const verifyEmailSchema = z.object({\n code: z.string().min(1, { message: 'Verification code is required' })\n});\n\n/**\n * Schema for resending verification email\n */\nexport const resendVerificationSchema = z.object({\n email: z.string().email().optional()\n});\n\n/**\n * Schema for biometric verification\n */\nexport const biometricVerifySchema = z.object({});\n\n/**\n * Schema for push token registration\n */\nexport const registerPushTokenSchema = z.object({\n pushToken: z.string().min(1, { message: 'Push token is required' })\n});\n\n/**\n * Schema for push token deregistration\n */\nexport const deregisterPushTokenSchema = z.object({\n pushToken: z.string().min(1, { message: 'Push token is required' })\n});\n\n/**\n * Schema for getting 2FA secret\n */\nexport const getTwofaSecretSchema = z.object({\n pushCode: z.string().min(6, { message: 'Push code is required' })\n});\n\n/**\n * Schema for disabling 2FA\n */\nexport const disableTwofaSchema = z.object({\n password: z.string().min(1, { message: 'Password is required' })\n});\n\n/**\n * Schema for logout\n */\nexport const logoutSchema = z.object({\n allDevices: z.boolean().optional().default(false)\n});\n\n/**\n * Schema for ending all sessions\n */\nexport const endAllSessionsSchema = z.object({\n skipCurrentSession: z.boolean().optional().default(true)\n});\n\n/**\n * Schema for OTP-based login request\n */\nexport const otpLoginRequestSchema = z.object({\n email: z.string().email({ message: 'Invalid email address' })\n});\n\n/**\n * Schema for OTP-based login verification\n */\nexport const otpLoginVerifySchema = z.object({\n email: z.string().email(),\n code: z.number().min(100000).max(999999)\n});\n\nexport type SignupInput = z.infer<typeof signupSchema>;\nexport type LoginInput = z.infer<typeof loginSchema>;\nexport type OAuthLoginInput = z.infer<typeof oAuthLoginSchema>;\nexport type ResetPasswordInput = z.infer<typeof resetPasswordSchema>;\nexport type ChangePasswordInput = z.infer<typeof changePasswordSchema>;\nexport type TwoFaVerifyInput = z.infer<typeof twoFaVerifySchema>;\nexport type VerifyEmailInput = z.infer<typeof verifyEmailSchema>;\nexport type LogoutInput = z.infer<typeof logoutSchema>;\n\n/** Schemas used by auth procedures */\nexport interface AuthSchemas {\n signup: AnyZodObject;\n login: AnyZodObject;\n oauth: AnyZodObject;\n}\n\n\n/**\n * Compute merged ZodObject type.\n * When TExt is defined, produces a schema with both base and extension shapes.\n * When TExt is undefined, produces the base schema.\n */\ntype MergedSchema<TBase extends AnyZodObject, TExt extends AnyZodObject | undefined> =\n [TExt] extends [AnyZodObject]\n ? z.ZodObject<TBase['shape'] & TExt['shape'], 'strip', z.ZodTypeAny>\n : TBase;\n\n/** Result type from createSchemas - preserves concrete schema types */\nexport type CreatedSchemas<TExtensions extends SchemaExtensions = {}> = {\n signup: MergedSchema<typeof signupSchema, TExtensions['signup']>;\n login: MergedSchema<typeof loginSchema, TExtensions['login']>;\n oauth: MergedSchema<typeof oAuthLoginSchema, TExtensions['oauth']>;\n};\n\nexport type SignupSchemaInput<TExtensions extends SchemaExtensions = {}> =\n SignupInput & (TExtensions['signup'] extends AnyZodObject ? z.infer<TExtensions['signup']> : {});\n\nexport type LoginSchemaInput<TExtensions extends SchemaExtensions = {}> =\n LoginInput & (TExtensions['login'] extends AnyZodObject ? z.infer<TExtensions['login']> : {});\n\nexport type OAuthSchemaInput<TExtensions extends SchemaExtensions = {}> =\n OAuthLoginInput & (TExtensions['oauth'] extends AnyZodObject ? z.infer<TExtensions['oauth']> : {});\n\n/** Create schemas with optional extensions merged in */\nexport function createSchemas<TExtensions extends SchemaExtensions = {}>(\n extensions?: TExtensions\n): CreatedSchemas<TExtensions> {\n return {\n signup: extensions?.signup\n ? signupSchema.merge(extensions.signup)\n : signupSchema,\n login: extensions?.login\n ? loginSchema.merge(extensions.login)\n : loginSchema,\n oauth: extensions?.oauth\n ? oAuthLoginSchema.merge(extensions.oauth)\n : oAuthLoginSchema\n } as CreatedSchemas<TExtensions>;\n}\n"],"mappings":"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","names":[]}
|