fixxed color selector in the category settings and fixxed drag and drop
Build APK / build (push) Canceled after 2m0s

subtask
This commit is contained in:
2026-08-09 21:42:54 +02:00
parent 4b6e87c979
commit 4c3d1a118c
99 changed files with 3335 additions and 920 deletions
+101 -9
View File
@@ -1,4 +1,7 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const express_1 = require("express");
const asyncHandler_1 = require("../utils/asyncHandler");
@@ -8,7 +11,47 @@ const drizzle_orm_1 = require("drizzle-orm");
const auth_1 = require("../utils/auth");
const errorHandler_1 = require("../middleware/errorHandler");
const zod_1 = require("zod");
const bcryptjs_1 = __importDefault(require("bcryptjs"));
const express_rate_limit_1 = __importDefault(require("express-rate-limit"));
const crypto_1 = __importDefault(require("crypto"));
const router = (0, express_1.Router)();
// Rate limiting for auth endpoints
const authLimiter = (0, express_rate_limit_1.default)({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 10, // limit each IP to 10 requests per windowMs
message: {
error: {
code: 'RATE_LIMITED',
message: 'Too many attempts. Please try again later.',
},
},
standardHeaders: true,
legacyHeaders: false,
});
const loginLimiter = (0, express_rate_limit_1.default)({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 5, // stricter limit for login
message: {
error: {
code: 'RATE_LIMITED',
message: 'Too many login attempts. Please try again later.',
},
},
standardHeaders: true,
legacyHeaders: false,
});
const passwordResetLimiter = (0, express_rate_limit_1.default)({
windowMs: 60 * 60 * 1000, // 1 hour
max: 3, // very strict for password reset
message: {
error: {
code: 'RATE_LIMITED',
message: 'Too many password reset requests. Please try again later.',
},
},
standardHeaders: true,
legacyHeaders: false,
});
const usernameSchema = zod_1.z
.string()
.min(3)
@@ -22,15 +65,24 @@ const loginSchema = zod_1.z.object({
username: zod_1.z.string(),
password: zod_1.z.string(),
});
// In production, use bcrypt or argon2 for password hashing
function hashPassword(password) {
// Simple hash for demo - replace with bcrypt in production
return Buffer.from(password).toString('base64');
const forgotPasswordSchema = zod_1.z.object({
username: zod_1.z.string().min(1),
});
const resetPasswordSchema = zod_1.z.object({
token: zod_1.z.string().min(1),
password: zod_1.z.string().min(8),
});
const BCRYPT_ROUNDS = 12;
async function hashPassword(password) {
return bcryptjs_1.default.hash(password, BCRYPT_ROUNDS);
}
function verifyPassword(password, hash) {
return hashPassword(password) === hash;
async function verifyPassword(password, hash) {
return bcryptjs_1.default.compare(password, hash);
}
router.post('/register', (0, asyncHandler_1.asyncHandler)(async (req, res) => {
function generateResetToken() {
return crypto_1.default.randomBytes(32).toString('hex');
}
router.post('/register', authLimiter, (0, asyncHandler_1.asyncHandler)(async (req, res) => {
const data = registerSchema.parse(req.body);
const existing = await db_1.db.select().from(schema_1.users).where((0, drizzle_orm_1.eq)(schema_1.users.username, data.username)).limit(1);
if (existing.length > 0) {
@@ -41,7 +93,7 @@ router.post('/register', (0, asyncHandler_1.asyncHandler)(async (req, res) => {
await db_1.db.insert(schema_1.users).values({
id: userId,
username: data.username,
passwordHash: hashPassword(data.password),
passwordHash: await hashPassword(data.password),
createdAt: now,
});
const token = (0, auth_1.generateToken)({ userId, username: data.username });
@@ -50,7 +102,7 @@ router.post('/register', (0, asyncHandler_1.asyncHandler)(async (req, res) => {
token,
});
}));
router.post('/login', (0, asyncHandler_1.asyncHandler)(async (req, res) => {
router.post('/login', loginLimiter, (0, asyncHandler_1.asyncHandler)(async (req, res) => {
const data = loginSchema.parse(req.body);
const user = await db_1.db.select().from(schema_1.users).where((0, drizzle_orm_1.eq)(schema_1.users.username, data.username)).limit(1);
if (user.length === 0 || !verifyPassword(data.password, user[0].passwordHash)) {
@@ -72,5 +124,45 @@ router.get('/me', (0, asyncHandler_1.asyncHandler)(async (req, res) => {
}
res.json({ id: user[0].id, username: user[0].username });
}));
router.post('/forgot-password', passwordResetLimiter, (0, asyncHandler_1.asyncHandler)(async (req, res) => {
const data = forgotPasswordSchema.parse(req.body);
const user = await db_1.db.select().from(schema_1.users).where((0, drizzle_orm_1.eq)(schema_1.users.username, data.username)).limit(1);
// Always return success to prevent username enumeration
if (user.length === 0) {
res.json({ success: true });
return;
}
const resetToken = generateResetToken();
const resetTokenExpiry = Date.now() + 3600000; // 1 hour
await db_1.db
.update(schema_1.users)
.set({ resetToken, resetTokenExpiry })
.where((0, drizzle_orm_1.eq)(schema_1.users.id, user[0].id));
// In production, send email with reset link
// For now, return token in response (dev only)
if (process.env.NODE_ENV !== 'production') {
res.json({ success: true, resetToken });
}
else {
res.json({ success: true });
}
}));
router.post('/reset-password', passwordResetLimiter, (0, asyncHandler_1.asyncHandler)(async (req, res) => {
const data = resetPasswordSchema.parse(req.body);
const user = await db_1.db
.select()
.from(schema_1.users)
.where((0, drizzle_orm_1.eq)(schema_1.users.resetToken, data.token))
.limit(1);
if (user.length === 0 || !user[0].resetTokenExpiry || user[0].resetTokenExpiry < Date.now()) {
throw new errorHandler_1.AppError('INVALID_TOKEN', 'Invalid or expired reset token', 400);
}
const newPasswordHash = await hashPassword(data.password);
await db_1.db
.update(schema_1.users)
.set({ passwordHash: newPasswordHash, resetToken: null, resetTokenExpiry: null })
.where((0, drizzle_orm_1.eq)(schema_1.users.id, user[0].id));
res.json({ success: true });
}));
exports.default = router;
//# sourceMappingURL=auth.js.map