first commit

This commit is contained in:
Дмитрий Мамедов
2026-06-10 16:23:41 +03:00
commit f37733a5c9
70 changed files with 16338 additions and 0 deletions

139
prisma/schema.prisma Normal file
View File

@@ -0,0 +1,139 @@
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
}
enum Role {
USER
ADMIN
}
enum QrStatus {
PENDING
SCANNED
CONFIRMED
EXPIRED
}
model User {
id String @id @default(uuid()) @db.Uuid
email String @unique
phone String? @unique
passwordHash String? @map("password_hash")
displayName String? @map("display_name")
avatarUrl String? @map("avatar_url")
role Role @default(USER)
isActive Boolean @default(true) @map("is_active")
emailVerifiedAt DateTime? @map("email_verified_at")
phoneVerifiedAt DateTime? @map("phone_verified_at")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
ownedClients Client[] @relation("ClientOwner")
sessions Session[]
auditLogs AuditLog[]
verificationCodes VerificationCode[]
qrSessions QrSession[]
@@map("users")
}
model Client {
id String @id @default(uuid()) @db.Uuid
name String
description String?
clientId String @unique @map("client_id")
clientSecret String? @map("client_secret")
redirectUris String[] @map("redirect_uris")
grants String[]
isConfidential Boolean @default(true) @map("is_confidential")
ownerId String? @map("owner_id") @db.Uuid
logoUrl String? @map("logo_url")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
owner User? @relation("ClientOwner", fields: [ownerId], references: [id], onDelete: SetNull)
sessions Session[]
@@map("clients")
}
model Session {
id String @id @default(uuid()) @db.Uuid
userId String @map("user_id") @db.Uuid
clientId String? @map("client_id") @db.Uuid
ip String?
userAgent String? @map("user_agent")
isActive Boolean @default(true) @map("is_active")
expiresAt DateTime @map("expires_at")
createdAt DateTime @default(now()) @map("created_at")
lastUsedAt DateTime @default(now()) @map("last_used_at")
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
client Client? @relation(fields: [clientId], references: [id], onDelete: SetNull)
refreshTokens RefreshToken[]
@@map("sessions")
}
model RefreshToken {
id String @id @default(uuid()) @db.Uuid
tokenHash String @unique @map("token_hash")
sessionId String @map("session_id") @db.Uuid
expiresAt DateTime @map("expires_at")
revoked Boolean @default(false)
createdAt DateTime @default(now()) @map("created_at")
session Session @relation(fields: [sessionId], references: [id], onDelete: Cascade)
@@map("refresh_tokens")
}
model VerificationCode {
id String @id @default(uuid()) @db.Uuid
userId String? @map("user_id") @db.Uuid
target String
channel String
code String
purpose String @default("AUTH")
expiresAt DateTime @map("expires_at")
usedAt DateTime? @map("used_at")
createdAt DateTime @default(now()) @map("created_at")
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
@@map("verification_codes")
}
model QrSession {
id String @id @default(uuid()) @db.Uuid
sessionId String @unique @map("session_id")
userId String? @map("user_id") @db.Uuid
status QrStatus @default(PENDING)
qrData String? @map("qr_data")
deviceInfo String? @map("device_info")
expiresAt DateTime @map("expires_at")
createdAt DateTime @default(now()) @map("created_at")
confirmedAt DateTime? @map("confirmed_at")
user User? @relation(fields: [userId], references: [id], onDelete: SetNull)
@@map("qr_sessions")
}
model AuditLog {
id String @id @default(uuid()) @db.Uuid
userId String? @map("user_id") @db.Uuid
action String
details Json?
ip String?
userAgent String? @map("user_agent")
createdAt DateTime @default(now()) @map("created_at")
user User? @relation(fields: [userId], references: [id], onDelete: SetNull)
@@map("audit_logs")
}

49
prisma/seed.ts Normal file
View File

@@ -0,0 +1,49 @@
import { PrismaClient, Role } from "@prisma/client";
import * as argon2 from "argon2";
import { randomBytes } from "node:crypto";
async function main(): Promise<void> {
const prisma = new PrismaClient({ datasourceUrl: process.env.DATABASE_URL });
const adminEmail = "admin@sso.local";
const adminPassword = "admin123!";
const existingAdmin = await prisma.user.findUnique({ where: { email: adminEmail } });
if (!existingAdmin) {
const passwordHash = await argon2.hash(adminPassword);
await prisma.user.create({
data: {
email: adminEmail,
passwordHash,
displayName: "SSO Admin",
role: Role.ADMIN,
},
});
console.log(`Admin user created: ${adminEmail} / ${adminPassword}`);
}
const testClientId = "test-client";
const existingClient = await prisma.client.findUnique({ where: { clientId: testClientId } });
if (!existingClient) {
const clientSecret = randomBytes(32).toString("hex");
await prisma.client.create({
data: {
name: "Test OAuth Client",
clientId: testClientId,
clientSecret,
redirectUris: ["http://localhost:5173/callback", "http://localhost:3000/callback"],
grants: ["authorization_code", "client_credentials", "refresh_token"],
isConfidential: true,
},
});
console.log(`OAuth client created: ${testClientId} / ${clientSecret}`);
}
console.log("Seed complete.");
await prisma.$disconnect();
}
main().catch((e) => {
console.error("Seed failed:", e);
process.exit(1);
});