Initial commit

This commit is contained in:
Jennie Robinson Faber 2025-11-11 19:12:21 +00:00
commit 92e96b9107
85 changed files with 24969 additions and 0 deletions

View file

@ -0,0 +1,100 @@
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");
}
});

View file

@ -0,0 +1,28 @@
export default defineEventHandler(async (event) => {
const config = useRuntimeConfig();
// Generate state for CSRF protection
const state = Math.random().toString(36).substring(7);
// Store state in session (you'll need to implement session storage)
setCookie(event, "oauth_state", state, {
httpOnly: true,
secure: true,
sameSite: "lax",
maxAge: 60 * 10, // 10 minutes
});
// Build OAuth authorization URL
const params = new URLSearchParams({
client_id: String(config.ghostguildClientId || ""),
redirect_uri: `${config.public.siteUrl}/api/auth/callback`,
response_type: "code",
scope: "read:user read:member",
state: state,
});
const authUrl = `${config.ghostguildApiUrl}/oauth/authorize?${params}`;
// Redirect to Ghost Guild OAuth
return sendRedirect(event, authUrl);
});

View file

@ -0,0 +1,10 @@
export default defineEventHandler(async (event) => {
// Clear the auth token cookie
deleteCookie(event, 'auth-token')
// Return success response
return {
success: true,
message: 'Logged out successfully'
}
})

View file

@ -0,0 +1,53 @@
import jwt from "jsonwebtoken";
import { User } from "../../models/User";
export default defineEventHandler(async (event) => {
const config = useRuntimeConfig();
const token = getCookie(event, "auth-token");
if (!token) {
throw createError({
statusCode: 401,
statusMessage: "Unauthorized - No token provided",
});
}
try {
// Verify and decode the token
const decoded = jwt.verify(token, config.jwtSecret as string) as any;
// Get fresh user data from database
const user = await User.findById(decoded.userId).select("-__v");
if (!user) {
throw createError({
statusCode: 404,
statusMessage: "User not found",
});
}
// Return user data (without sensitive fields)
return {
id: user._id,
username: user.username,
displayName: user.displayName,
email: user.email,
avatar: user.avatar,
roles: user.roles,
permissions: user.permissions,
contributions: user.contributions,
};
} catch (error: any) {
if (
error.name === "JsonWebTokenError" ||
error.name === "TokenExpiredError"
) {
deleteCookie(event, "auth-token");
throw createError({
statusCode: 401,
statusMessage: "Invalid or expired token",
});
}
throw error;
}
});