158 lines
3.7 KiB
TypeScript
158 lines
3.7 KiB
TypeScript
import { eq } from 'drizzle-orm'
|
|
import { createHash, randomUUID } from 'node:crypto'
|
|
import type { AppDb } from './index.js'
|
|
import {
|
|
refreshSessions,
|
|
userApps,
|
|
userPermissions,
|
|
users,
|
|
} from './schema/index.js'
|
|
|
|
export type UserRow = typeof users.$inferSelect
|
|
|
|
export function hashToken(token: string): string {
|
|
return createHash('sha256').update(token).digest('hex')
|
|
}
|
|
|
|
export function listUsers(db: AppDb): UserRow[] {
|
|
return db.select().from(users).all()
|
|
}
|
|
|
|
export function getUserById(db: AppDb, id: string): UserRow | undefined {
|
|
return db.select().from(users).where(eq(users.id, id)).get()
|
|
}
|
|
|
|
export function getUserByEmail(
|
|
db: AppDb,
|
|
email: string,
|
|
): UserRow | undefined {
|
|
return db
|
|
.select()
|
|
.from(users)
|
|
.where(eq(users.email, email.toLowerCase()))
|
|
.get()
|
|
}
|
|
|
|
export function getUserApps(db: AppDb, userId: string): string[] {
|
|
return db
|
|
.select()
|
|
.from(userApps)
|
|
.where(eq(userApps.userId, userId))
|
|
.all()
|
|
.map((r) => r.appId)
|
|
}
|
|
|
|
export function getUserPermissions(db: AppDb, userId: string): string[] {
|
|
return db
|
|
.select()
|
|
.from(userPermissions)
|
|
.where(eq(userPermissions.userId, userId))
|
|
.all()
|
|
.map((r) => r.permission)
|
|
}
|
|
|
|
export function createUser(
|
|
db: AppDb,
|
|
input: {
|
|
email: string
|
|
name: string
|
|
passwordHash: string
|
|
isAdmin: boolean
|
|
},
|
|
): UserRow {
|
|
const now = new Date().toISOString()
|
|
const id = randomUUID()
|
|
db.insert(users)
|
|
.values({
|
|
id,
|
|
email: input.email.toLowerCase(),
|
|
name: input.name,
|
|
passwordHash: input.passwordHash,
|
|
isAdmin: input.isAdmin,
|
|
disabled: false,
|
|
createdAt: now,
|
|
updatedAt: now,
|
|
})
|
|
.run()
|
|
return getUserById(db, id)!
|
|
}
|
|
|
|
export function updateUser(
|
|
db: AppDb,
|
|
id: string,
|
|
patch: {
|
|
email?: string
|
|
name?: string
|
|
passwordHash?: string
|
|
isAdmin?: boolean
|
|
disabled?: boolean
|
|
},
|
|
): UserRow | undefined {
|
|
const existing = getUserById(db, id)
|
|
if (!existing) return undefined
|
|
const now = new Date().toISOString()
|
|
db.update(users)
|
|
.set({
|
|
email: patch.email?.toLowerCase() ?? existing.email,
|
|
name: patch.name ?? existing.name,
|
|
passwordHash: patch.passwordHash ?? existing.passwordHash,
|
|
isAdmin: patch.isAdmin ?? existing.isAdmin,
|
|
disabled: patch.disabled ?? existing.disabled,
|
|
updatedAt: now,
|
|
})
|
|
.where(eq(users.id, id))
|
|
.run()
|
|
return getUserById(db, id)
|
|
}
|
|
|
|
export function deleteUser(db: AppDb, id: string): boolean {
|
|
const result = db.delete(users).where(eq(users.id, id)).run()
|
|
return result.changes > 0
|
|
}
|
|
|
|
export function setUserAccess(
|
|
db: AppDb,
|
|
userId: string,
|
|
apps: string[],
|
|
permissions: string[],
|
|
): void {
|
|
db.delete(userApps).where(eq(userApps.userId, userId)).run()
|
|
db.delete(userPermissions).where(eq(userPermissions.userId, userId)).run()
|
|
for (const appId of apps) {
|
|
db.insert(userApps).values({ userId, appId }).run()
|
|
}
|
|
for (const permission of permissions) {
|
|
db.insert(userPermissions).values({ userId, permission }).run()
|
|
}
|
|
db.update(users)
|
|
.set({ updatedAt: new Date().toISOString() })
|
|
.where(eq(users.id, userId))
|
|
.run()
|
|
}
|
|
|
|
export function createRefreshSession(
|
|
db: AppDb,
|
|
userId: string,
|
|
rawToken: string,
|
|
expiresAt: Date,
|
|
): void {
|
|
db.insert(refreshSessions)
|
|
.values({
|
|
id: randomUUID(),
|
|
userId,
|
|
tokenHash: hashToken(rawToken),
|
|
expiresAt: expiresAt.toISOString(),
|
|
revokedAt: null,
|
|
createdAt: new Date().toISOString(),
|
|
})
|
|
.run()
|
|
}
|
|
|
|
export function revokeRefreshSession(db: AppDb, rawToken: string): void {
|
|
const now = new Date().toISOString()
|
|
db.update(refreshSessions)
|
|
.set({ revokedAt: now })
|
|
.where(eq(refreshSessions.tokenHash, hashToken(rawToken)))
|
|
.run()
|
|
}
|