100 lines
2.9 KiB
TypeScript
100 lines
2.9 KiB
TypeScript
import { User } from "../../models/User";
|
|
import jwt from "jsonwebtoken";
|
|
import type {
|
|
OAuthTokenResponse,
|
|
GhostGuildUserInfo,
|
|
} from "../../../types/auth";
|
|
|
|
export default defineEventHandler(async (event) => {
|
|
const config = useRuntimeConfig();
|
|
const query = getQuery(event);
|
|
|
|
// Verify state for CSRF protection
|
|
const storedState = getCookie(event, "oauth_state");
|
|
if (!storedState || storedState !== query.state) {
|
|
return sendRedirect(event, "/login?error=invalid_state");
|
|
}
|
|
|
|
// Clear the state cookie
|
|
deleteCookie(event, "oauth_state");
|
|
|
|
// Exchange authorization code for access token
|
|
try {
|
|
const tokenResponse = await $fetch<OAuthTokenResponse>(
|
|
`${config.ghostguildApiUrl}/oauth/token`,
|
|
{
|
|
method: "POST",
|
|
body: {
|
|
grant_type: "authorization_code",
|
|
code: query.code,
|
|
redirect_uri: `${config.public.siteUrl}/api/auth/callback`,
|
|
client_id: config.ghostguildClientId,
|
|
client_secret: config.ghostguildClientSecret,
|
|
},
|
|
},
|
|
);
|
|
|
|
// Get user information from Ghost Guild
|
|
const userInfo = await $fetch<GhostGuildUserInfo>(
|
|
`${config.ghostguildApiUrl}/user/me`,
|
|
{
|
|
headers: {
|
|
Authorization: `Bearer ${tokenResponse.access_token}`,
|
|
},
|
|
},
|
|
);
|
|
|
|
// Find or create user in our database
|
|
let user = await User.findOne({ ghostguildId: userInfo.id });
|
|
|
|
if (!user) {
|
|
user = await User.create({
|
|
ghostguildId: userInfo.id,
|
|
email: userInfo.email,
|
|
username: userInfo.username,
|
|
displayName: userInfo.displayName || userInfo.username,
|
|
avatar: userInfo.avatar,
|
|
roles: userInfo.roles || ["member"],
|
|
permissions: {
|
|
canEdit: userInfo.roles?.includes("member") || false,
|
|
canModerate: userInfo.roles?.includes("moderator") || false,
|
|
canAdmin: userInfo.roles?.includes("admin") || false,
|
|
},
|
|
lastLogin: new Date(),
|
|
});
|
|
} else {
|
|
// Update existing user
|
|
user.displayName = userInfo.displayName || userInfo.username;
|
|
user.avatar = userInfo.avatar;
|
|
user.roles = userInfo.roles || ["member"];
|
|
user.lastLogin = new Date();
|
|
await user.save();
|
|
}
|
|
|
|
// Create JWT token
|
|
const token = jwt.sign(
|
|
{
|
|
userId: user._id,
|
|
username: user.username,
|
|
roles: user.roles,
|
|
permissions: user.permissions,
|
|
},
|
|
config.jwtSecret as string,
|
|
{ expiresIn: "7d" },
|
|
);
|
|
|
|
// Set JWT as httpOnly cookie
|
|
setCookie(event, "auth-token", token, {
|
|
httpOnly: true,
|
|
secure: true,
|
|
sameSite: "lax",
|
|
maxAge: 60 * 60 * 24 * 7, // 7 days
|
|
});
|
|
|
|
// Redirect to dashboard or home
|
|
return sendRedirect(event, "/dashboard");
|
|
} catch (error) {
|
|
console.error("OAuth callback error:", error);
|
|
return sendRedirect(event, "/login?error=authentication_failed");
|
|
}
|
|
});
|