feat(chat): threads UI + graphql flow + qwen/gigachat integration
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import type { H3Event } from "h3";
|
||||
import { getCookie, setCookie, deleteCookie, getHeader } from "h3";
|
||||
import { prisma } from "./prisma";
|
||||
import { hashPassword } from "./password";
|
||||
|
||||
export type AuthContext = {
|
||||
teamId: string;
|
||||
@@ -58,9 +59,16 @@ export async function getAuthContext(event: H3Event): Promise<AuthContext> {
|
||||
|
||||
const user = await prisma.user.findUnique({ where: { id: userId } });
|
||||
const team = await prisma.team.findUnique({ where: { id: teamId } });
|
||||
const conv = await prisma.chatConversation.findUnique({ where: { id: conversationId } });
|
||||
|
||||
if (!user || !team || !conv) {
|
||||
if (!user || !team) {
|
||||
throw createError({ statusCode: 401, statusMessage: "Unauthorized" });
|
||||
}
|
||||
|
||||
const conv = await prisma.chatConversation.findFirst({
|
||||
where: { id: conversationId, teamId: team.id, createdByUserId: user.id },
|
||||
});
|
||||
|
||||
if (!conv) {
|
||||
throw createError({ statusCode: 401, statusMessage: "Unauthorized" });
|
||||
}
|
||||
|
||||
@@ -68,10 +76,11 @@ export async function getAuthContext(event: H3Event): Promise<AuthContext> {
|
||||
}
|
||||
|
||||
export async function ensureDemoAuth() {
|
||||
const demoPasswordHash = hashPassword("DemoPass123!");
|
||||
const user = await prisma.user.upsert({
|
||||
where: { id: "demo-user" },
|
||||
update: { email: "demo@clientsflow.local", name: "Demo User" },
|
||||
create: { id: "demo-user", email: "demo@clientsflow.local", name: "Demo User" },
|
||||
update: { phone: "+15550000099", email: "demo@clientsflow.local", passwordHash: demoPasswordHash, name: "Demo User" },
|
||||
create: { id: "demo-user", phone: "+15550000099", email: "demo@clientsflow.local", passwordHash: demoPasswordHash, name: "Demo User" },
|
||||
});
|
||||
const team = await prisma.team.upsert({
|
||||
where: { id: "demo-team" },
|
||||
|
||||
29
Frontend/server/utils/password.ts
Normal file
29
Frontend/server/utils/password.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { randomBytes, scryptSync, timingSafeEqual } from "node:crypto";
|
||||
|
||||
const SCRYPT_KEY_LENGTH = 64;
|
||||
|
||||
export function normalizePhone(raw: string) {
|
||||
const trimmed = (raw ?? "").trim();
|
||||
if (!trimmed) return "";
|
||||
const hasPlus = trimmed.startsWith("+");
|
||||
const digits = trimmed.replace(/\D/g, "");
|
||||
if (!digits) return "";
|
||||
return `${hasPlus ? "+" : ""}${digits}`;
|
||||
}
|
||||
|
||||
export function hashPassword(password: string) {
|
||||
const salt = randomBytes(16).toString("base64url");
|
||||
const digest = scryptSync(password, salt, SCRYPT_KEY_LENGTH).toString("base64url");
|
||||
return `scrypt$${salt}$${digest}`;
|
||||
}
|
||||
|
||||
export function verifyPassword(password: string, encodedHash: string) {
|
||||
const [algo, salt, digest] = (encodedHash ?? "").split("$");
|
||||
if (algo !== "scrypt" || !salt || !digest) return false;
|
||||
|
||||
const actual = scryptSync(password, salt, SCRYPT_KEY_LENGTH);
|
||||
const expected = Buffer.from(digest, "base64url");
|
||||
if (actual.byteLength !== expected.byteLength) return false;
|
||||
|
||||
return timingSafeEqual(actual, expected);
|
||||
}
|
||||
Reference in New Issue
Block a user