mirror of
https://github.com/stackblitz-labs/bolt.diy
synced 2025-06-26 18:26:38 +00:00
- Add GitHub OAuth 2.0 authentication flow - Create authentication routes: /auth/login, /auth/callback, /auth/logout - Implement OAuth server utilities with proper session management - Add authentication hooks and client-side state management - Update header with login/logout controls and user menu - Create user profile page with GitHub integration - Add environment variables for GitHub OAuth configuration - Include comprehensive documentation for setup and usage - Enhance profile store with authentication state - Add authentication status API endpoint Closes: GitHub authentication implementation
135 lines
3.6 KiB
TypeScript
135 lines
3.6 KiB
TypeScript
import { useEffect, useState } from 'react';
|
|
import { useNavigate, useLocation } from '@remix-run/react';
|
|
import {
|
|
profileStore,
|
|
setAuthenticatedUser,
|
|
clearAuthState,
|
|
isAuthenticated as getIsAuthenticated,
|
|
getGitHubUser
|
|
} from '~/lib/stores/profile';
|
|
import { useStore } from '@nanostores/react';
|
|
import type { GitHubUser } from '~/lib/auth/github-oauth.server';
|
|
|
|
// Define the API response type for auth status
|
|
interface AuthStatusResponse {
|
|
isAuthenticated: boolean;
|
|
user?: GitHubUser;
|
|
tokenStatus?: {
|
|
hasToken: boolean;
|
|
};
|
|
error?: string;
|
|
}
|
|
|
|
/**
|
|
* Hook to manage authentication state and sync between server and client
|
|
*/
|
|
export function useAuth() {
|
|
const navigate = useNavigate();
|
|
const location = useLocation();
|
|
const profile = useStore(profileStore);
|
|
const [isLoading, setIsLoading] = useState(true);
|
|
const [serverChecked, setServerChecked] = useState(false);
|
|
|
|
// Sync server authentication state with client on initial load
|
|
useEffect(() => {
|
|
const checkServerAuth = async () => {
|
|
try {
|
|
setIsLoading(true);
|
|
// Fetch authentication status from server
|
|
const response = await fetch('/api/auth/status');
|
|
|
|
if (response.ok) {
|
|
const data = await response.json() as AuthStatusResponse;
|
|
|
|
if (data.isAuthenticated && data.user) {
|
|
// Update client state with server auth data
|
|
setAuthenticatedUser(data.user);
|
|
} else if (profile.isAuthenticated) {
|
|
// Clear client state if server says not authenticated
|
|
clearAuthState();
|
|
}
|
|
} else {
|
|
// Handle error - assume not authenticated
|
|
if (profile.isAuthenticated) {
|
|
clearAuthState();
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('Error checking authentication status:', error);
|
|
} finally {
|
|
setIsLoading(false);
|
|
setServerChecked(true);
|
|
}
|
|
};
|
|
|
|
// Only check server auth once
|
|
if (!serverChecked) {
|
|
checkServerAuth();
|
|
}
|
|
}, [serverChecked, profile.isAuthenticated]);
|
|
|
|
/**
|
|
* Navigate to login page
|
|
*/
|
|
const login = (redirectTo?: string) => {
|
|
const searchParams = new URLSearchParams();
|
|
|
|
if (redirectTo || location.pathname !== '/login') {
|
|
searchParams.set('redirectTo', redirectTo || location.pathname);
|
|
}
|
|
|
|
const searchParamsString = searchParams.toString();
|
|
navigate(`/auth/login${searchParamsString ? `?${searchParamsString}` : ''}`);
|
|
};
|
|
|
|
/**
|
|
* Navigate to logout page
|
|
*/
|
|
const logout = (redirectTo?: string) => {
|
|
const searchParams = new URLSearchParams();
|
|
|
|
if (redirectTo) {
|
|
searchParams.set('redirectTo', redirectTo);
|
|
}
|
|
|
|
const searchParamsString = searchParams.toString();
|
|
navigate(`/auth/logout${searchParamsString ? `?${searchParamsString}` : ''}`);
|
|
};
|
|
|
|
/**
|
|
* Check if user needs to authenticate for a protected resource
|
|
* and redirect to login if needed
|
|
*/
|
|
const requireAuth = (redirectIfNotAuth: boolean = true) => {
|
|
if (!isLoading && !profile.isAuthenticated && redirectIfNotAuth) {
|
|
login();
|
|
return false;
|
|
}
|
|
|
|
return profile.isAuthenticated;
|
|
};
|
|
|
|
return {
|
|
// Authentication state
|
|
isAuthenticated: profile.isAuthenticated,
|
|
isLoading,
|
|
|
|
// User data
|
|
user: profile.isAuthenticated ? {
|
|
username: profile.username,
|
|
avatar: profile.avatar,
|
|
bio: profile.bio,
|
|
githubUser: profile.github,
|
|
lastLogin: profile.lastLogin
|
|
} : null,
|
|
|
|
// GitHub specific data
|
|
githubUser: getGitHubUser(),
|
|
|
|
// Actions
|
|
login,
|
|
logout,
|
|
requireAuth,
|
|
};
|
|
}
|