test commit
This commit is contained in:
+107
-69
@@ -1,139 +1,177 @@
|
||||
import { issueEmailCode, normalizeEmail, verifyEmailCode } from '../lib/auth.js'
|
||||
import { prisma } from '../lib/prisma.js'
|
||||
import { NOTIFICATION_EVENTS } from '../../shared/constants/notification-events.js'
|
||||
import {
|
||||
issueEmailCode,
|
||||
normalizeEmail,
|
||||
verifyEmailCode,
|
||||
} from "../lib/auth.js";
|
||||
import { prisma } from "../lib/prisma.js";
|
||||
import { NOTIFICATION_EVENTS } from "../../../shared/constants/notification-events.js";
|
||||
|
||||
function mapUserForClient(user) {
|
||||
const adminEmail = normalizeEmail(process.env.ADMIN_EMAIL)
|
||||
const userEmail = normalizeEmail(user.email)
|
||||
const adminEmail = normalizeEmail(process.env.ADMIN_EMAIL);
|
||||
const userEmail = normalizeEmail(user.email);
|
||||
return {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
phone: user.phone,
|
||||
isAdmin: Boolean(adminEmail) && userEmail === adminEmail,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export async function registerAuthRoutes(fastify) {
|
||||
fastify.post('/api/auth/request-code', async (request, reply) => {
|
||||
const email = normalizeEmail(request.body?.email)
|
||||
if (!email || !email.includes('@')) return reply.code(400).send({ error: 'Некорректная почта' })
|
||||
fastify.post("/api/auth/request-code", async (request, reply) => {
|
||||
const email = normalizeEmail(request.body?.email);
|
||||
if (!email || !email.includes("@"))
|
||||
return reply.code(400).send({ error: "Некорректная почта" });
|
||||
|
||||
const code = await issueEmailCode({ email, purpose: 'login' })
|
||||
const code = await issueEmailCode({ email, purpose: "login" });
|
||||
|
||||
const adminEmail = process.env.ADMIN_EMAIL?.trim().toLowerCase()
|
||||
const isAdmin = email === adminEmail
|
||||
const adminEmail = process.env.ADMIN_EMAIL?.trim().toLowerCase();
|
||||
const isAdmin = email === adminEmail;
|
||||
|
||||
request.server.eventBus.emit(NOTIFICATION_EVENTS.AUTH_CODE_REQUESTED, {
|
||||
email,
|
||||
code,
|
||||
isAdmin,
|
||||
})
|
||||
});
|
||||
|
||||
return { ok: true }
|
||||
})
|
||||
return { ok: true };
|
||||
});
|
||||
|
||||
fastify.post('/api/auth/verify-code', async (request, reply) => {
|
||||
const email = normalizeEmail(request.body?.email)
|
||||
const code = String(request.body?.code || '').trim()
|
||||
if (!email || !email.includes('@')) return reply.code(400).send({ error: 'Некорректная почта' })
|
||||
if (!code || code.length !== 6) return reply.code(400).send({ error: 'Код должен быть из 6 цифр' })
|
||||
fastify.post("/api/auth/verify-code", async (request, reply) => {
|
||||
const email = normalizeEmail(request.body?.email);
|
||||
const code = String(request.body?.code || "").trim();
|
||||
if (!email || !email.includes("@"))
|
||||
return reply.code(400).send({ error: "Некорректная почта" });
|
||||
if (!code || code.length !== 6)
|
||||
return reply.code(400).send({ error: "Код должен быть из 6 цифр" });
|
||||
|
||||
const ok = await verifyEmailCode({ email, purpose: 'login', code })
|
||||
if (!ok) return reply.code(401).send({ error: 'Неверный или истёкший код' })
|
||||
const ok = await verifyEmailCode({ email, purpose: "login", code });
|
||||
if (!ok)
|
||||
return reply.code(401).send({ error: "Неверный или истёкший код" });
|
||||
|
||||
const user = await prisma.user.upsert({
|
||||
where: { email },
|
||||
update: {},
|
||||
create: { email },
|
||||
})
|
||||
});
|
||||
|
||||
// Ensure notification preference exists
|
||||
await prisma.notificationPreference.upsert({
|
||||
where: { userId: user.id },
|
||||
create: { userId: user.id, globalEnabled: true },
|
||||
update: {},
|
||||
})
|
||||
});
|
||||
|
||||
const token = fastify.jwt.sign({ sub: user.id, email: user.email })
|
||||
return { token, user: mapUserForClient(user) }
|
||||
})
|
||||
const token = fastify.jwt.sign({ sub: user.id, email: user.email });
|
||||
return { token, user: mapUserForClient(user) };
|
||||
});
|
||||
|
||||
fastify.get(
|
||||
'/api/me',
|
||||
"/api/me",
|
||||
{ preHandler: [fastify.authenticate] },
|
||||
async (request) => {
|
||||
const userId = request.user.sub
|
||||
const user = await prisma.user.findUnique({ where: { id: userId } })
|
||||
if (!user) return { user: null }
|
||||
return { user: mapUserForClient(user) }
|
||||
const userId = request.user.sub;
|
||||
const user = await prisma.user.findUnique({ where: { id: userId } });
|
||||
if (!user) return { user: null };
|
||||
return { user: mapUserForClient(user) };
|
||||
},
|
||||
)
|
||||
);
|
||||
|
||||
fastify.post(
|
||||
'/api/me/change-email/request-code',
|
||||
"/api/me/change-email/request-code",
|
||||
{ preHandler: [fastify.authenticate] },
|
||||
async (request, reply) => {
|
||||
const userId = request.user.sub
|
||||
const newEmail = normalizeEmail(request.body?.newEmail)
|
||||
if (!newEmail || !newEmail.includes('@')) return reply.code(400).send({ error: 'Некорректная почта' })
|
||||
const userId = request.user.sub;
|
||||
const newEmail = normalizeEmail(request.body?.newEmail);
|
||||
if (!newEmail || !newEmail.includes("@"))
|
||||
return reply.code(400).send({ error: "Некорректная почта" });
|
||||
|
||||
const exists = await prisma.user.findUnique({ where: { email: newEmail } })
|
||||
if (exists) return reply.code(409).send({ error: 'Эта почта уже занята' })
|
||||
const exists = await prisma.user.findUnique({
|
||||
where: { email: newEmail },
|
||||
});
|
||||
if (exists)
|
||||
return reply.code(409).send({ error: "Эта почта уже занята" });
|
||||
|
||||
await issueEmailCode({ email: newEmail, purpose: 'change_email', userId })
|
||||
return { ok: true }
|
||||
await issueEmailCode({
|
||||
email: newEmail,
|
||||
purpose: "change_email",
|
||||
userId,
|
||||
});
|
||||
return { ok: true };
|
||||
},
|
||||
)
|
||||
);
|
||||
|
||||
fastify.post(
|
||||
'/api/me/change-email/verify',
|
||||
"/api/me/change-email/verify",
|
||||
{ preHandler: [fastify.authenticate] },
|
||||
async (request, reply) => {
|
||||
const userId = request.user.sub
|
||||
const newEmail = normalizeEmail(request.body?.newEmail)
|
||||
const code = String(request.body?.code || '').trim()
|
||||
if (!newEmail || !newEmail.includes('@')) return reply.code(400).send({ error: 'Некорректная почта' })
|
||||
if (!code || code.length !== 6) return reply.code(400).send({ error: 'Код должен быть из 6 цифр' })
|
||||
const userId = request.user.sub;
|
||||
const newEmail = normalizeEmail(request.body?.newEmail);
|
||||
const code = String(request.body?.code || "").trim();
|
||||
if (!newEmail || !newEmail.includes("@"))
|
||||
return reply.code(400).send({ error: "Некорректная почта" });
|
||||
if (!code || code.length !== 6)
|
||||
return reply.code(400).send({ error: "Код должен быть из 6 цифр" });
|
||||
|
||||
const exists = await prisma.user.findUnique({ where: { email: newEmail } })
|
||||
if (exists) return reply.code(409).send({ error: 'Эта почта уже занята' })
|
||||
const exists = await prisma.user.findUnique({
|
||||
where: { email: newEmail },
|
||||
});
|
||||
if (exists)
|
||||
return reply.code(409).send({ error: "Эта почта уже занята" });
|
||||
|
||||
const ok = await verifyEmailCode({ email: newEmail, purpose: 'change_email', code, userId })
|
||||
if (!ok) return reply.code(401).send({ error: 'Неверный или истёкший код' })
|
||||
const ok = await verifyEmailCode({
|
||||
email: newEmail,
|
||||
purpose: "change_email",
|
||||
code,
|
||||
userId,
|
||||
});
|
||||
if (!ok)
|
||||
return reply.code(401).send({ error: "Неверный или истёкший код" });
|
||||
|
||||
const user = await prisma.user.update({
|
||||
where: { id: userId },
|
||||
data: { email: newEmail },
|
||||
})
|
||||
return { user: mapUserForClient(user) }
|
||||
});
|
||||
return { user: mapUserForClient(user) };
|
||||
},
|
||||
)
|
||||
);
|
||||
|
||||
fastify.patch(
|
||||
'/api/me/profile',
|
||||
"/api/me/profile",
|
||||
{ preHandler: [fastify.authenticate] },
|
||||
async (request, reply) => {
|
||||
const userId = request.user.sub
|
||||
const nameRaw = request.body?.name
|
||||
const name = nameRaw === null || nameRaw === undefined ? null : String(nameRaw).trim()
|
||||
const phoneRaw = request.body?.phone
|
||||
const phone = phoneRaw === null || phoneRaw === undefined ? null : String(phoneRaw).trim()
|
||||
const userId = request.user.sub;
|
||||
const nameRaw = request.body?.name;
|
||||
const name =
|
||||
nameRaw === null || nameRaw === undefined
|
||||
? null
|
||||
: String(nameRaw).trim();
|
||||
const phoneRaw = request.body?.phone;
|
||||
const phone =
|
||||
phoneRaw === null || phoneRaw === undefined
|
||||
? null
|
||||
: String(phoneRaw).trim();
|
||||
|
||||
if (name !== null && name.length > 40) return reply.code(400).send({ error: 'Имя/ник максимум 40 символов' })
|
||||
if (name !== null && name.length > 40)
|
||||
return reply.code(400).send({ error: "Имя/ник максимум 40 символов" });
|
||||
if (phone !== null) {
|
||||
const compact = phone.replace(/[\s()-]/g, '')
|
||||
if (compact.length > 20) return reply.code(400).send({ error: 'Телефон слишком длинный' })
|
||||
const compact = phone.replace(/[\s()-]/g, "");
|
||||
if (compact.length > 20)
|
||||
return reply.code(400).send({ error: "Телефон слишком длинный" });
|
||||
if (compact.length && !/^\+?\d{7,20}$/.test(compact)) {
|
||||
return reply.code(400).send({ error: 'Некорректный телефон' })
|
||||
return reply.code(400).send({ error: "Некорректный телефон" });
|
||||
}
|
||||
}
|
||||
|
||||
const updated = await prisma.user.update({
|
||||
where: { id: userId },
|
||||
data: { name: name && name.length ? name : null, phone: phone && phone.length ? phone : null },
|
||||
})
|
||||
return { user: mapUserForClient(updated) }
|
||||
data: {
|
||||
name: name && name.length ? name : null,
|
||||
phone: phone && phone.length ? phone : null,
|
||||
},
|
||||
});
|
||||
return { user: mapUserForClient(updated) };
|
||||
},
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user