53 lines
1.7 KiB
TypeScript
53 lines
1.7 KiB
TypeScript
import {
|
|
integer,
|
|
json,
|
|
pgTable,
|
|
text,
|
|
timestamp,
|
|
varchar,
|
|
} from "drizzle-orm/pg-core";
|
|
import { user } from "./better.auth.schema";
|
|
import { relations } from "drizzle-orm";
|
|
|
|
export const twoFactor = pgTable("two_factor", {
|
|
id: text("id").primaryKey(),
|
|
secret: text("secret").notNull(),
|
|
backupCodes: json("backup_codes").$type<string[]>(),
|
|
userId: text("user_id")
|
|
.notNull()
|
|
.references(() => user.id, { onDelete: "cascade" }),
|
|
createdAt: timestamp("created_at").notNull(),
|
|
updatedAt: timestamp("updated_at").notNull(),
|
|
});
|
|
|
|
export const twofaSessions = pgTable("twofa_sessions", {
|
|
id: text("id").primaryKey(),
|
|
userId: text("user_id")
|
|
.notNull()
|
|
.references(() => user.id, { onDelete: "cascade" }),
|
|
sessionId: text("session_id").notNull(), // Better Auth session ID
|
|
|
|
// Verification Tracking
|
|
verificationToken: text("verification_token").notNull().unique(), // Unique nonce for this attempt
|
|
codeUsed: text("code_used"), // The TOTP code submitted (prevent replay)
|
|
status: varchar("status", { length: 16 }).notNull(), // "pending" | "verified" | "failed" | "expired"
|
|
|
|
attempts: integer("attempts").default(0).notNull(),
|
|
maxAttempts: integer("max_attempts").default(5).notNull(),
|
|
|
|
verifiedAt: timestamp("verified_at"),
|
|
expiresAt: timestamp("expires_at").notNull(),
|
|
createdAt: timestamp("created_at").notNull(),
|
|
|
|
// Security Audit
|
|
ipAddress: text("ip_address").default(""),
|
|
userAgent: text("user_agent").default(""),
|
|
});
|
|
|
|
export const twofaSessionsRelations = relations(twofaSessions, ({ one }) => ({
|
|
userAccount: one(user, {
|
|
fields: [twofaSessions.userId],
|
|
references: [user.id],
|
|
}),
|
|
}));
|