feat(#4): Implement Authentik OIDC authentication with BetterAuth

- Integrated BetterAuth library for modern authentication
- Added Session, Account, and Verification database tables
- Created complete auth module with service, controller, guards, and decorators
- Implemented shared authentication types in @mosaic/shared package
- Added comprehensive test coverage (26 tests passing)
- Documented type sharing strategy for monorepo
- Updated environment configuration with OIDC and JWT settings

Key architectural decisions:
- BetterAuth over Passport.js for better TypeScript support
- Separation of User (DB entity) vs AuthUser (client-safe subset)
- Shared types package to prevent FE/BE drift
- Factory pattern for auth config to use shared Prisma instance

Ready for frontend integration (Issue #6).

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

Fixes #4
This commit is contained in:
Jason Woltje
2026-01-28 17:26:34 -06:00
parent 139a16648d
commit 6a038d093b
22 changed files with 2616 additions and 7 deletions

View File

@@ -70,6 +70,8 @@ model User {
id String @id @default(uuid()) @db.Uuid
email String @unique
name String
emailVerified Boolean @default(false) @map("email_verified")
image String?
authProviderId String? @unique @map("auth_provider_id")
preferences Json @default("{}")
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
@@ -83,6 +85,8 @@ model User {
createdEvents Event[] @relation("EventCreator")
createdProjects Project[] @relation("ProjectCreator")
activityLogs ActivityLog[]
sessions Session[]
accounts Account[]
@@map("users")
}
@@ -251,3 +255,60 @@ model MemoryEmbedding {
@@index([workspaceId])
@@map("memory_embeddings")
}
// ============================================
// AUTHENTICATION MODELS (BetterAuth)
// ============================================
model Session {
id String @id @default(uuid()) @db.Uuid
userId String @map("user_id") @db.Uuid
token String @unique
expiresAt DateTime @map("expires_at") @db.Timestamptz
ipAddress String? @map("ip_address")
userAgent String? @map("user_agent")
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
// Relations
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@index([userId])
@@index([token])
@@map("sessions")
}
model Account {
id String @id @default(uuid()) @db.Uuid
userId String @map("user_id") @db.Uuid
accountId String @map("account_id")
providerId String @map("provider_id")
accessToken String? @map("access_token")
refreshToken String? @map("refresh_token")
idToken String? @map("id_token")
accessTokenExpiresAt DateTime? @map("access_token_expires_at") @db.Timestamptz
refreshTokenExpiresAt DateTime? @map("refresh_token_expires_at") @db.Timestamptz
scope String?
password String?
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
// Relations
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([providerId, accountId])
@@index([userId])
@@map("accounts")
}
model Verification {
id String @id @default(uuid()) @db.Uuid
identifier String
value String
expiresAt DateTime @map("expires_at") @db.Timestamptz
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
@@index([identifier])
@@map("verifications")
}