drop notification, reget deps
This commit is contained in:
@@ -67,148 +67,10 @@ export async function authenticateUser(
|
||||
return { user, sessionToken: session.sessionToken, accessToken };
|
||||
}
|
||||
|
||||
const GOOGLE_ISSUER = "https://accounts.google.com";
|
||||
const APPLE_ISSUER = "https://appleid.apple.com";
|
||||
const APPLE_JWKS_URL = new URL("https://appleid.apple.com/auth/keys");
|
||||
|
||||
/**
|
||||
* Verifies a Google ID token using firebase-admin and returns the user.
|
||||
* If the user does not exist, creates a new account.
|
||||
* If the user exists but has not linked Google, links the provider.
|
||||
*/
|
||||
export async function authenticateWithGoogle(idToken: string) {
|
||||
const { initializeApp, cert, getApps } = await import("firebase-admin/app");
|
||||
|
||||
// Initialize Firebase Admin if not already done
|
||||
if (getApps().length === 0) {
|
||||
// Try to load from environment or use application default credentials
|
||||
const projectId = process.env.FIREBASE_PROJECT_ID;
|
||||
const clientEmail = process.env.FIREBASE_CLIENT_EMAIL;
|
||||
const privateKey = process.env.FIREBASE_PRIVATE_KEY;
|
||||
|
||||
if (projectId && clientEmail && privateKey) {
|
||||
initializeApp({
|
||||
credential: cert({
|
||||
projectId,
|
||||
clientEmail,
|
||||
privateKey: privateKey.replace(/\\n/g, "\n"),
|
||||
}),
|
||||
});
|
||||
} else {
|
||||
// Fall back to application default credentials
|
||||
initializeApp({ projectId: projectId ?? "kordant" });
|
||||
}
|
||||
}
|
||||
|
||||
let decodedToken: { uid: string; email?: string; name?: string; picture?: string };
|
||||
try {
|
||||
const authModule = await import("firebase-admin/auth");
|
||||
decodedToken = await authModule.getAuth().verifyIdToken(idToken);
|
||||
} catch (err) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "Invalid Google ID token",
|
||||
});
|
||||
}
|
||||
|
||||
const googleUserId = decodedToken.uid;
|
||||
const email = decodedToken.email;
|
||||
const name = decodedToken.name ?? email?.split("@")[0] ?? "User";
|
||||
const avatarUrl = decodedToken.picture ?? null;
|
||||
|
||||
if (!email) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "Google account has no email address",
|
||||
});
|
||||
}
|
||||
|
||||
// Check if this Google account is already linked
|
||||
const [existingAccount] = await db
|
||||
.select()
|
||||
.from(accounts)
|
||||
.where(
|
||||
and(
|
||||
eq(accounts.provider, "google"),
|
||||
eq(accounts.providerAccountId, googleUserId),
|
||||
),
|
||||
)
|
||||
.limit(1);
|
||||
|
||||
let userId: string;
|
||||
let isNewUser = false;
|
||||
|
||||
if (existingAccount) {
|
||||
// Already linked — use the existing user
|
||||
userId = existingAccount.userId;
|
||||
isNewUser = false;
|
||||
|
||||
// Update the access token if provided
|
||||
await db
|
||||
.update(accounts)
|
||||
.set({
|
||||
accessToken: idToken,
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
.where(eq(accounts.id, existingAccount.id));
|
||||
} else {
|
||||
// Not linked — check if a user with this email exists
|
||||
const [existingUserByEmail] = await db
|
||||
.select()
|
||||
.from(users)
|
||||
.where(and(eq(users.email, email), isNull(users.deletedAt)))
|
||||
.limit(1);
|
||||
|
||||
if (existingUserByEmail) {
|
||||
// Link Google to existing user
|
||||
userId = existingUserByEmail.id;
|
||||
isNewUser = false;
|
||||
await db.insert(accounts).values({
|
||||
userId,
|
||||
provider: "google",
|
||||
providerAccountId: googleUserId,
|
||||
accessToken: idToken,
|
||||
});
|
||||
|
||||
// Update avatar if not set
|
||||
if (!existingUserByEmail.image && avatarUrl) {
|
||||
await db.update(users).set({ image: avatarUrl }).where(eq(users.id, userId));
|
||||
}
|
||||
} else {
|
||||
// Create new user with Google
|
||||
isNewUser = true;
|
||||
const [newUser] = await db
|
||||
.insert(users)
|
||||
.values({
|
||||
name,
|
||||
email,
|
||||
image: avatarUrl,
|
||||
emailVerified: new Date(),
|
||||
})
|
||||
.returning();
|
||||
userId = newUser.id;
|
||||
|
||||
await db.insert(accounts).values({
|
||||
userId,
|
||||
provider: "google",
|
||||
providerAccountId: googleUserId,
|
||||
accessToken: idToken,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Create session and JWT
|
||||
const session = await createSession(userId);
|
||||
const accessToken = await signJWT({ sub: userId }, { expiresIn: "7d" });
|
||||
const refreshToken = await signJWT({ sub: userId, type: "refresh" }, { expiresIn: "30d" });
|
||||
|
||||
const [user] = await db.select().from(users).where(eq(users.id, userId)).limit(1);
|
||||
if (!user) {
|
||||
throw new TRPCError({ code: "NOT_FOUND", message: "User not found after creation" });
|
||||
}
|
||||
|
||||
return { user, sessionToken: session.sessionToken, accessToken, refreshToken, isNewUser };
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies an Apple identity token and authenticates the user.
|
||||
|
||||
Reference in New Issue
Block a user