create-charcole 2.2.0 → 2.2.2
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/.github/workflows/release.yml +26 -26
- package/CHANGELOG.md +301 -301
- package/LICENSE +21 -21
- package/README.md +357 -354
- package/bin/index.js +494 -444
- package/bin/lib/pkgManager.js +49 -49
- package/bin/lib/templateHandler.js +33 -33
- package/package.json +42 -27
- package/packages/swagger/BACKWARD_COMPATIBILITY.md +1 -1
- package/packages/swagger/CHANGELOG.md +1 -1
- package/packages/swagger/README.md +3 -3
- package/packages/swagger/package.json +57 -44
- package/packages/swagger/src/index.d.ts +126 -126
- package/packages/swagger/src/index.js +12 -12
- package/packages/swagger/src/setup.js +100 -100
- package/template/js/.env.example +16 -15
- package/template/js/README.md +982 -978
- package/template/js/basePackage.json +26 -26
- package/template/js/src/app.js +81 -81
- package/template/js/src/config/constants.js +20 -20
- package/template/js/src/config/env.js +26 -26
- package/template/js/src/config/swagger.config.js +15 -15
- package/template/js/src/lib/swagger/SWAGGER_GUIDE.md +3 -3
- package/template/js/src/middlewares/errorHandler.js +180 -180
- package/template/js/src/middlewares/requestLogger.js +33 -33
- package/template/js/src/middlewares/validateRequest.js +42 -42
- package/template/js/src/modules/auth/auth.constants.js +3 -3
- package/template/js/src/modules/auth/auth.controller.js +29 -29
- package/template/js/src/modules/auth/auth.middlewares.js +19 -19
- package/template/js/src/modules/auth/auth.routes.js +131 -131
- package/template/js/src/modules/auth/auth.schemas.js +60 -60
- package/template/js/src/modules/auth/auth.service.js +67 -67
- package/template/js/src/modules/auth/package.json +6 -6
- package/template/js/src/modules/health/controller.js +151 -151
- package/template/js/src/modules/swagger/package.json +5 -5
- package/template/js/src/repositories/user.repo.js +19 -19
- package/template/js/src/routes/index.js +25 -25
- package/template/js/src/routes/protected.js +57 -57
- package/template/js/src/server.js +38 -38
- package/template/js/src/utils/AppError.js +182 -182
- package/template/js/src/utils/logger.js +73 -73
- package/template/js/src/utils/response.js +51 -51
- package/template/ts/.env.example +16 -15
- package/template/ts/README.md +982 -978
- package/template/ts/basePackage.json +36 -36
- package/template/ts/build.js +46 -46
- package/template/ts/src/app.ts +71 -71
- package/template/ts/src/config/constants.ts +27 -27
- package/template/ts/src/config/env.ts +40 -40
- package/template/ts/src/config/swagger.config.ts +30 -30
- package/template/ts/src/lib/swagger/SWAGGER_GUIDE.md +2 -2
- package/template/ts/src/middlewares/errorHandler.ts +201 -201
- package/template/ts/src/middlewares/requestLogger.ts +38 -38
- package/template/ts/src/middlewares/validateRequest.ts +46 -46
- package/template/ts/src/modules/auth/auth.constants.ts +6 -6
- package/template/ts/src/modules/auth/auth.controller.ts +32 -32
- package/template/ts/src/modules/auth/auth.middlewares.ts +46 -46
- package/template/ts/src/modules/auth/auth.routes.ts +52 -52
- package/template/ts/src/modules/auth/auth.schemas.ts +73 -73
- package/template/ts/src/modules/auth/auth.service.ts +106 -106
- package/template/ts/src/modules/auth/package.json +10 -10
- package/template/ts/src/modules/health/controller.ts +80 -80
- package/template/ts/src/modules/swagger/package.json +5 -5
- package/template/ts/src/repositories/user.repo.ts +33 -33
- package/template/ts/src/routes/index.ts +24 -24
- package/template/ts/src/routes/protected.ts +46 -46
- package/template/ts/src/server.ts +41 -41
- package/template/ts/src/types/express.d.ts +9 -9
- package/template/ts/src/utils/AppError.ts +220 -220
- package/template/ts/src/utils/logger.ts +55 -55
- package/template/ts/src/utils/response.ts +100 -100
- package/template/ts/tsconfig.json +26 -26
- package/packages/swagger/package-lock.json +0 -1715
- package/tmpclaude-1049-cwd +0 -1
- package/tmpclaude-3e37-cwd +0 -1
- package/tmpclaude-4d73-cwd +0 -1
- package/tmpclaude-8a8e-cwd +0 -1
|
@@ -1,46 +1,46 @@
|
|
|
1
|
-
import jwt from "jsonwebtoken";
|
|
2
|
-
import { Request, Response, NextFunction } from "express";
|
|
3
|
-
|
|
4
|
-
type JwtPayload = {
|
|
5
|
-
sub: string;
|
|
6
|
-
email: string;
|
|
7
|
-
role: string;
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
// Extend Express Request type to include user property
|
|
11
|
-
declare global {
|
|
12
|
-
namespace Express {
|
|
13
|
-
interface Request {
|
|
14
|
-
user?: JwtPayload;
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export const requireAuth = (
|
|
20
|
-
req: Request,
|
|
21
|
-
res: Response,
|
|
22
|
-
next: NextFunction,
|
|
23
|
-
): void => {
|
|
24
|
-
const authHeader = req.headers.authorization;
|
|
25
|
-
|
|
26
|
-
if (!authHeader?.startsWith("Bearer ")) {
|
|
27
|
-
res.status(401).json({ message: "Unauthorized" });
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const token = authHeader.split(" ")[1];
|
|
32
|
-
|
|
33
|
-
try {
|
|
34
|
-
const secret = process.env.JWT_SECRET;
|
|
35
|
-
|
|
36
|
-
if (!secret) {
|
|
37
|
-
throw new Error("JWT_SECRET environment variable is not defined");
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const payload = jwt.verify(token, secret) as JwtPayload;
|
|
41
|
-
req.user = payload;
|
|
42
|
-
next();
|
|
43
|
-
} catch {
|
|
44
|
-
res.status(401).json({ message: "Invalid token" });
|
|
45
|
-
}
|
|
46
|
-
};
|
|
1
|
+
import jwt from "jsonwebtoken";
|
|
2
|
+
import { Request, Response, NextFunction } from "express";
|
|
3
|
+
|
|
4
|
+
type JwtPayload = {
|
|
5
|
+
sub: string;
|
|
6
|
+
email: string;
|
|
7
|
+
role: string;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
// Extend Express Request type to include user property
|
|
11
|
+
declare global {
|
|
12
|
+
namespace Express {
|
|
13
|
+
interface Request {
|
|
14
|
+
user?: JwtPayload;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const requireAuth = (
|
|
20
|
+
req: Request,
|
|
21
|
+
res: Response,
|
|
22
|
+
next: NextFunction,
|
|
23
|
+
): void => {
|
|
24
|
+
const authHeader = req.headers.authorization;
|
|
25
|
+
|
|
26
|
+
if (!authHeader?.startsWith("Bearer ")) {
|
|
27
|
+
res.status(401).json({ message: "Unauthorized" });
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const token = authHeader.split(" ")[1];
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
const secret = process.env.JWT_SECRET;
|
|
35
|
+
|
|
36
|
+
if (!secret) {
|
|
37
|
+
throw new Error("JWT_SECRET environment variable is not defined");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const payload = jwt.verify(token, secret) as JwtPayload;
|
|
41
|
+
req.user = payload;
|
|
42
|
+
next();
|
|
43
|
+
} catch {
|
|
44
|
+
res.status(401).json({ message: "Invalid token" });
|
|
45
|
+
}
|
|
46
|
+
};
|
|
@@ -1,52 +1,52 @@
|
|
|
1
|
-
import { Router } from "express";
|
|
2
|
-
import { AuthController } from "./auth.controller.ts";
|
|
3
|
-
|
|
4
|
-
const router = Router();
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* @swagger
|
|
8
|
-
* /api/auth/register:
|
|
9
|
-
* post:
|
|
10
|
-
* summary: Register a new user
|
|
11
|
-
* description: Create a new user account with email and password
|
|
12
|
-
* tags:
|
|
13
|
-
* - Authentication
|
|
14
|
-
* requestBody:
|
|
15
|
-
* required: true
|
|
16
|
-
* content:
|
|
17
|
-
* application/json:
|
|
18
|
-
* schema:
|
|
19
|
-
* $ref: '#/components/schemas/registerSchema'
|
|
20
|
-
* responses:
|
|
21
|
-
* 201:
|
|
22
|
-
* $ref: '#/components/responses/Success'
|
|
23
|
-
* 400:
|
|
24
|
-
* $ref: '#/components/responses/ValidationError'
|
|
25
|
-
*/
|
|
26
|
-
router.post("/register", AuthController.register);
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* @swagger
|
|
30
|
-
* /api/auth/login:
|
|
31
|
-
* post:
|
|
32
|
-
* summary: Login user
|
|
33
|
-
* description: Authenticate user with email and password
|
|
34
|
-
* tags:
|
|
35
|
-
* - Authentication
|
|
36
|
-
* requestBody:
|
|
37
|
-
* required: true
|
|
38
|
-
* content:
|
|
39
|
-
* application/json:
|
|
40
|
-
* schema:
|
|
41
|
-
* $ref: '#/components/schemas/loginSchema'
|
|
42
|
-
* responses:
|
|
43
|
-
* 200:
|
|
44
|
-
* $ref: '#/components/responses/Success'
|
|
45
|
-
* 401:
|
|
46
|
-
* $ref: '#/components/responses/Unauthorized'
|
|
47
|
-
* 400:
|
|
48
|
-
* $ref: '#/components/responses/ValidationError'
|
|
49
|
-
*/
|
|
50
|
-
router.post("/login", AuthController.login);
|
|
51
|
-
|
|
52
|
-
export default router;
|
|
1
|
+
import { Router } from "express";
|
|
2
|
+
import { AuthController } from "./auth.controller.ts";
|
|
3
|
+
|
|
4
|
+
const router = Router();
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @swagger
|
|
8
|
+
* /api/auth/register:
|
|
9
|
+
* post:
|
|
10
|
+
* summary: Register a new user
|
|
11
|
+
* description: Create a new user account with email and password
|
|
12
|
+
* tags:
|
|
13
|
+
* - Authentication
|
|
14
|
+
* requestBody:
|
|
15
|
+
* required: true
|
|
16
|
+
* content:
|
|
17
|
+
* application/json:
|
|
18
|
+
* schema:
|
|
19
|
+
* $ref: '#/components/schemas/registerSchema'
|
|
20
|
+
* responses:
|
|
21
|
+
* 201:
|
|
22
|
+
* $ref: '#/components/responses/Success'
|
|
23
|
+
* 400:
|
|
24
|
+
* $ref: '#/components/responses/ValidationError'
|
|
25
|
+
*/
|
|
26
|
+
router.post("/register", AuthController.register);
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @swagger
|
|
30
|
+
* /api/auth/login:
|
|
31
|
+
* post:
|
|
32
|
+
* summary: Login user
|
|
33
|
+
* description: Authenticate user with email and password
|
|
34
|
+
* tags:
|
|
35
|
+
* - Authentication
|
|
36
|
+
* requestBody:
|
|
37
|
+
* required: true
|
|
38
|
+
* content:
|
|
39
|
+
* application/json:
|
|
40
|
+
* schema:
|
|
41
|
+
* $ref: '#/components/schemas/loginSchema'
|
|
42
|
+
* responses:
|
|
43
|
+
* 200:
|
|
44
|
+
* $ref: '#/components/responses/Success'
|
|
45
|
+
* 401:
|
|
46
|
+
* $ref: '#/components/responses/Unauthorized'
|
|
47
|
+
* 400:
|
|
48
|
+
* $ref: '#/components/responses/ValidationError'
|
|
49
|
+
*/
|
|
50
|
+
router.post("/login", AuthController.login);
|
|
51
|
+
|
|
52
|
+
export default router;
|
|
@@ -1,73 +1,73 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import { USER_ROLES, AUTH_PROVIDERS } from "./auth.constants.ts";
|
|
3
|
-
|
|
4
|
-
export const emailSchema = z
|
|
5
|
-
.string()
|
|
6
|
-
.email("Invalid email address")
|
|
7
|
-
.toLowerCase();
|
|
8
|
-
|
|
9
|
-
export const passwordSchema = z
|
|
10
|
-
.string()
|
|
11
|
-
.min(8, "Password must be at least 8 characters")
|
|
12
|
-
.max(72, "Password too long");
|
|
13
|
-
|
|
14
|
-
export const userSchema = z.object({
|
|
15
|
-
id: z.string().uuid(),
|
|
16
|
-
email: emailSchema,
|
|
17
|
-
name: z.string().min(1).max(100),
|
|
18
|
-
role: z.enum(USER_ROLES).default("user"),
|
|
19
|
-
provider: z.enum(AUTH_PROVIDERS).default("credentials"),
|
|
20
|
-
|
|
21
|
-
passwordHash: z.string().optional(), // credentials only
|
|
22
|
-
isEmailVerified: z.boolean().default(false),
|
|
23
|
-
|
|
24
|
-
createdAt: z.date(),
|
|
25
|
-
updatedAt: z.date(),
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
export const registerSchema = z.object({
|
|
29
|
-
name: z.string().min(1, "Name is required"),
|
|
30
|
-
email: emailSchema,
|
|
31
|
-
password: passwordSchema,
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
export const loginSchema = z.object({
|
|
35
|
-
email: emailSchema,
|
|
36
|
-
password: z.string().min(1, "Password is required"),
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
export const jwtPayloadSchema = z.object({
|
|
40
|
-
sub: z.string().uuid(), // user id
|
|
41
|
-
email: emailSchema,
|
|
42
|
-
role: z.enum(USER_ROLES),
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
export const forgotPasswordSchema = z.object({
|
|
46
|
-
email: emailSchema,
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
export const resetPasswordSchema = z.object({
|
|
50
|
-
token: z.string().min(1),
|
|
51
|
-
newPassword: passwordSchema,
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
export const publicUserSchema = userSchema.omit({
|
|
55
|
-
passwordHash: true,
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
// Generic validate function with proper typing
|
|
59
|
-
export const validate = <T extends z.ZodTypeAny>(
|
|
60
|
-
schema: T,
|
|
61
|
-
data: unknown,
|
|
62
|
-
): z.infer<T> => {
|
|
63
|
-
return schema.parse(data);
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
// Export inferred types for use in other files
|
|
67
|
-
export type User = z.infer<typeof userSchema>;
|
|
68
|
-
export type RegisterInput = z.infer<typeof registerSchema>;
|
|
69
|
-
export type LoginInput = z.infer<typeof loginSchema>;
|
|
70
|
-
export type JwtPayload = z.infer<typeof jwtPayloadSchema>;
|
|
71
|
-
export type ForgotPasswordInput = z.infer<typeof forgotPasswordSchema>;
|
|
72
|
-
export type ResetPasswordInput = z.infer<typeof resetPasswordSchema>;
|
|
73
|
-
export type PublicUser = z.infer<typeof publicUserSchema>;
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { USER_ROLES, AUTH_PROVIDERS } from "./auth.constants.ts";
|
|
3
|
+
|
|
4
|
+
export const emailSchema = z
|
|
5
|
+
.string()
|
|
6
|
+
.email("Invalid email address")
|
|
7
|
+
.toLowerCase();
|
|
8
|
+
|
|
9
|
+
export const passwordSchema = z
|
|
10
|
+
.string()
|
|
11
|
+
.min(8, "Password must be at least 8 characters")
|
|
12
|
+
.max(72, "Password too long");
|
|
13
|
+
|
|
14
|
+
export const userSchema = z.object({
|
|
15
|
+
id: z.string().uuid(),
|
|
16
|
+
email: emailSchema,
|
|
17
|
+
name: z.string().min(1).max(100),
|
|
18
|
+
role: z.enum(USER_ROLES).default("user"),
|
|
19
|
+
provider: z.enum(AUTH_PROVIDERS).default("credentials"),
|
|
20
|
+
|
|
21
|
+
passwordHash: z.string().optional(), // credentials only
|
|
22
|
+
isEmailVerified: z.boolean().default(false),
|
|
23
|
+
|
|
24
|
+
createdAt: z.date(),
|
|
25
|
+
updatedAt: z.date(),
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
export const registerSchema = z.object({
|
|
29
|
+
name: z.string().min(1, "Name is required"),
|
|
30
|
+
email: emailSchema,
|
|
31
|
+
password: passwordSchema,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
export const loginSchema = z.object({
|
|
35
|
+
email: emailSchema,
|
|
36
|
+
password: z.string().min(1, "Password is required"),
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
export const jwtPayloadSchema = z.object({
|
|
40
|
+
sub: z.string().uuid(), // user id
|
|
41
|
+
email: emailSchema,
|
|
42
|
+
role: z.enum(USER_ROLES),
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
export const forgotPasswordSchema = z.object({
|
|
46
|
+
email: emailSchema,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
export const resetPasswordSchema = z.object({
|
|
50
|
+
token: z.string().min(1),
|
|
51
|
+
newPassword: passwordSchema,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
export const publicUserSchema = userSchema.omit({
|
|
55
|
+
passwordHash: true,
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// Generic validate function with proper typing
|
|
59
|
+
export const validate = <T extends z.ZodTypeAny>(
|
|
60
|
+
schema: T,
|
|
61
|
+
data: unknown,
|
|
62
|
+
): z.infer<T> => {
|
|
63
|
+
return schema.parse(data);
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// Export inferred types for use in other files
|
|
67
|
+
export type User = z.infer<typeof userSchema>;
|
|
68
|
+
export type RegisterInput = z.infer<typeof registerSchema>;
|
|
69
|
+
export type LoginInput = z.infer<typeof loginSchema>;
|
|
70
|
+
export type JwtPayload = z.infer<typeof jwtPayloadSchema>;
|
|
71
|
+
export type ForgotPasswordInput = z.infer<typeof forgotPasswordSchema>;
|
|
72
|
+
export type ResetPasswordInput = z.infer<typeof resetPasswordSchema>;
|
|
73
|
+
export type PublicUser = z.infer<typeof publicUserSchema>;
|
|
@@ -1,106 +1,106 @@
|
|
|
1
|
-
import bcrypt from "bcryptjs";
|
|
2
|
-
import jwt from "jsonwebtoken";
|
|
3
|
-
import { registerSchema, loginSchema } from "./auth.schemas.ts";
|
|
4
|
-
import type { z } from "zod";
|
|
5
|
-
|
|
6
|
-
const SALT_ROUNDS = 10;
|
|
7
|
-
const JWT_EXPIRES_IN = "7d";
|
|
8
|
-
|
|
9
|
-
// Type definitions
|
|
10
|
-
type RegisterInput = z.infer<typeof registerSchema>;
|
|
11
|
-
type LoginInput = z.infer<typeof loginSchema>;
|
|
12
|
-
|
|
13
|
-
type User = {
|
|
14
|
-
id: string;
|
|
15
|
-
email: string;
|
|
16
|
-
name: string;
|
|
17
|
-
passwordHash: string;
|
|
18
|
-
role: string;
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
type UserRepo = {
|
|
22
|
-
findByEmail(email: string): Promise<User | null>;
|
|
23
|
-
create(data: {
|
|
24
|
-
email: string;
|
|
25
|
-
name: string;
|
|
26
|
-
passwordHash: string;
|
|
27
|
-
}): Promise<User>;
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
type JwtPayload = {
|
|
31
|
-
sub: string;
|
|
32
|
-
email: string;
|
|
33
|
-
role: string;
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
type LoginResult = {
|
|
37
|
-
user: User;
|
|
38
|
-
token: string;
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
export const AuthService = {
|
|
42
|
-
async hashPassword(password: string): Promise<string> {
|
|
43
|
-
return bcrypt.hash(password, SALT_ROUNDS);
|
|
44
|
-
},
|
|
45
|
-
|
|
46
|
-
async comparePassword(password: string, hash: string): Promise<boolean> {
|
|
47
|
-
return bcrypt.compare(password, hash);
|
|
48
|
-
},
|
|
49
|
-
|
|
50
|
-
signToken(payload: JwtPayload): string {
|
|
51
|
-
const secret = process.env.JWT_SECRET;
|
|
52
|
-
|
|
53
|
-
if (!secret) {
|
|
54
|
-
throw new Error("JWT_SECRET environment variable is not defined");
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
return jwt.sign(payload, secret, {
|
|
58
|
-
expiresIn: JWT_EXPIRES_IN,
|
|
59
|
-
});
|
|
60
|
-
},
|
|
61
|
-
|
|
62
|
-
async register(data: unknown, userRepo: UserRepo): Promise<User> {
|
|
63
|
-
const input: RegisterInput = registerSchema.parse(data);
|
|
64
|
-
|
|
65
|
-
const existingUser = await userRepo.findByEmail(input.email);
|
|
66
|
-
if (existingUser) {
|
|
67
|
-
throw new Error("Email already in use");
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const passwordHash = await this.hashPassword(input.password);
|
|
71
|
-
|
|
72
|
-
const user = await userRepo.create({
|
|
73
|
-
email: input.email,
|
|
74
|
-
name: input.name,
|
|
75
|
-
passwordHash,
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
return user;
|
|
79
|
-
},
|
|
80
|
-
|
|
81
|
-
async login(data: unknown, userRepo: UserRepo): Promise<LoginResult> {
|
|
82
|
-
const input: LoginInput = loginSchema.parse(data);
|
|
83
|
-
|
|
84
|
-
const user = await userRepo.findByEmail(input.email);
|
|
85
|
-
if (!user || !user.passwordHash) {
|
|
86
|
-
throw new Error("Invalid credentials");
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
const isValid = await this.comparePassword(
|
|
90
|
-
input.password,
|
|
91
|
-
user.passwordHash,
|
|
92
|
-
);
|
|
93
|
-
|
|
94
|
-
if (!isValid) {
|
|
95
|
-
throw new Error("Invalid credentials");
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const token = this.signToken({
|
|
99
|
-
sub: user.id,
|
|
100
|
-
email: user.email,
|
|
101
|
-
role: user.role,
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
return { user, token };
|
|
105
|
-
},
|
|
106
|
-
};
|
|
1
|
+
import bcrypt from "bcryptjs";
|
|
2
|
+
import jwt from "jsonwebtoken";
|
|
3
|
+
import { registerSchema, loginSchema } from "./auth.schemas.ts";
|
|
4
|
+
import type { z } from "zod";
|
|
5
|
+
|
|
6
|
+
const SALT_ROUNDS = 10;
|
|
7
|
+
const JWT_EXPIRES_IN = "7d";
|
|
8
|
+
|
|
9
|
+
// Type definitions
|
|
10
|
+
type RegisterInput = z.infer<typeof registerSchema>;
|
|
11
|
+
type LoginInput = z.infer<typeof loginSchema>;
|
|
12
|
+
|
|
13
|
+
type User = {
|
|
14
|
+
id: string;
|
|
15
|
+
email: string;
|
|
16
|
+
name: string;
|
|
17
|
+
passwordHash: string;
|
|
18
|
+
role: string;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
type UserRepo = {
|
|
22
|
+
findByEmail(email: string): Promise<User | null>;
|
|
23
|
+
create(data: {
|
|
24
|
+
email: string;
|
|
25
|
+
name: string;
|
|
26
|
+
passwordHash: string;
|
|
27
|
+
}): Promise<User>;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
type JwtPayload = {
|
|
31
|
+
sub: string;
|
|
32
|
+
email: string;
|
|
33
|
+
role: string;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
type LoginResult = {
|
|
37
|
+
user: User;
|
|
38
|
+
token: string;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export const AuthService = {
|
|
42
|
+
async hashPassword(password: string): Promise<string> {
|
|
43
|
+
return bcrypt.hash(password, SALT_ROUNDS);
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
async comparePassword(password: string, hash: string): Promise<boolean> {
|
|
47
|
+
return bcrypt.compare(password, hash);
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
signToken(payload: JwtPayload): string {
|
|
51
|
+
const secret = process.env.JWT_SECRET;
|
|
52
|
+
|
|
53
|
+
if (!secret) {
|
|
54
|
+
throw new Error("JWT_SECRET environment variable is not defined");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return jwt.sign(payload, secret, {
|
|
58
|
+
expiresIn: JWT_EXPIRES_IN,
|
|
59
|
+
});
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
async register(data: unknown, userRepo: UserRepo): Promise<User> {
|
|
63
|
+
const input: RegisterInput = registerSchema.parse(data);
|
|
64
|
+
|
|
65
|
+
const existingUser = await userRepo.findByEmail(input.email);
|
|
66
|
+
if (existingUser) {
|
|
67
|
+
throw new Error("Email already in use");
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const passwordHash = await this.hashPassword(input.password);
|
|
71
|
+
|
|
72
|
+
const user = await userRepo.create({
|
|
73
|
+
email: input.email,
|
|
74
|
+
name: input.name,
|
|
75
|
+
passwordHash,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
return user;
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
async login(data: unknown, userRepo: UserRepo): Promise<LoginResult> {
|
|
82
|
+
const input: LoginInput = loginSchema.parse(data);
|
|
83
|
+
|
|
84
|
+
const user = await userRepo.findByEmail(input.email);
|
|
85
|
+
if (!user || !user.passwordHash) {
|
|
86
|
+
throw new Error("Invalid credentials");
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const isValid = await this.comparePassword(
|
|
90
|
+
input.password,
|
|
91
|
+
user.passwordHash,
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
if (!isValid) {
|
|
95
|
+
throw new Error("Invalid credentials");
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const token = this.signToken({
|
|
99
|
+
sub: user.id,
|
|
100
|
+
email: user.email,
|
|
101
|
+
role: user.role,
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
return { user, token };
|
|
105
|
+
},
|
|
106
|
+
};
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
{
|
|
2
|
-
"dependencies": {
|
|
3
|
-
"bcryptjs": "^3.0.3",
|
|
4
|
-
"jsonwebtoken": "^9.0.3"
|
|
5
|
-
},
|
|
6
|
-
"devDependencies": {
|
|
7
|
-
"@types/bcryptjs": "^2.4.6",
|
|
8
|
-
"@types/jsonwebtoken": "^9.0.10"
|
|
9
|
-
}
|
|
10
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"dependencies": {
|
|
3
|
+
"bcryptjs": "^3.0.3",
|
|
4
|
+
"jsonwebtoken": "^9.0.3"
|
|
5
|
+
},
|
|
6
|
+
"devDependencies": {
|
|
7
|
+
"@types/bcryptjs": "^2.4.6",
|
|
8
|
+
"@types/jsonwebtoken": "^9.0.10"
|
|
9
|
+
}
|
|
10
|
+
}
|