courtmitra / apps /api /src /auth /auth.service.ts
Hetansh Waghela
fix: Critical auth + ownership + container bugs from audit
76b46a3
Raw
History Blame
2.44 kB
import { Injectable, Inject, UnauthorizedException } from "@nestjs/common";
import { eq, and } from "drizzle-orm";
import { users, userIdentities, type Database } from "@courtmitra/db";
import { DB } from "../db/db.module.js";
export interface ClerkUser {
id: string;
emailAddresses: { emailAddress: string }[];
firstName: string | null;
lastName: string | null;
imageUrl: string | null;
}
@Injectable()
export class AuthService {
constructor(@Inject(DB) private readonly db: Database) {}
/**
* Find or create a user from Clerk OAuth data.
* Maps Clerk userId -> our users table via userIdentities.
*/
async findOrCreateUser(clerkUser: ClerkUser): Promise<{ id: string; email: string | null }> {
const providerSubject = clerkUser.id;
// Try to find existing identity by provider + providerSubject (CRITICAL: must match the specific Clerk user)
const existingIdentity = await this.db
.select({ userId: userIdentities.userId })
.from(userIdentities)
.where(
and(
eq(userIdentities.provider, "google"),
eq(userIdentities.providerSubject, providerSubject),
),
)
.limit(1);
if (existingIdentity[0]) {
const user = await this.db
.select()
.from(users)
.where(eq(users.id, existingIdentity[0].userId))
.limit(1);
if (user[0]) {
return { id: user[0].id, email: user[0].email ?? null };
}
}
// Create new user
const email = clerkUser.emailAddresses[0]?.emailAddress ?? null;
const fullName = [clerkUser.firstName, clerkUser.lastName].filter(Boolean).join(" ") || null;
const [userRow] = await this.db
.insert(users)
.values({
displayName: fullName ?? email ?? "User",
email,
avatarUrl: clerkUser.imageUrl,
fullName,
})
.returning();
if (!userRow) {
throw new UnauthorizedException("Failed to create user");
}
// Create identity link
await this.db.insert(userIdentities).values({
userId: userRow.id,
provider: "google",
providerSubject,
isVerified: true,
metadata: { source: "clerk_oauth" },
});
return { id: userRow.id, email };
}
/** Look up a user by their internal DB id. */
async findUserById(id: string) {
const rows = await this.db.select().from(users).where(eq(users.id, id)).limit(1);
return rows[0] ?? null;
}
}