mirror of
https://github.com/stackblitz-labs/bolt.diy
synced 2025-06-26 18:26:38 +00:00
github oauth fixes and deployment enhancements
This commit is contained in:
parent
6b9c270a69
commit
2340edec94
32
.github/workflows/ecr-deploy.yaml
vendored
32
.github/workflows/ecr-deploy.yaml
vendored
@ -109,7 +109,7 @@ jobs:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
platforms: linux/amd64,linux/arm64
|
||||
target: buildify-development
|
||||
target: buildify-production
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
@ -174,9 +174,9 @@ jobs:
|
||||
fi
|
||||
echo "Image pushed successfully: ${{ steps.ecr-registry.outputs.registry }}:$TAG"
|
||||
|
||||
# Create PR for Kubernetes manifests update (only for main branch)
|
||||
# Create PR for Kubernetes manifests update (for main and dev branches)
|
||||
- name: Create PR for Kubernetes updates
|
||||
if: github.ref == 'refs/heads/main'
|
||||
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev'
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
@ -192,22 +192,21 @@ jobs:
|
||||
if [[ "${{ github.ref }}" == refs/tags/* ]]; then
|
||||
# For tagged releases, use the tag name
|
||||
IMAGE_TAG="${{ github.ref_name }}"
|
||||
sed -i "s|891377135844.dkr.ecr.us-east-1.amazonaws.com/buildify:dev|891377135844.dkr.ecr.us-east-1.amazonaws.com/buildify:$IMAGE_TAG|g" k8s/deployment.yaml
|
||||
elif [[ "${{ github.ref }}" == "refs/heads/main" ]]; then
|
||||
# For main branch, use 'latest'
|
||||
IMAGE_TAG="latest"
|
||||
sed -i "s|891377135844.dkr.ecr.us-east-1.amazonaws.com/buildify:dev|891377135844.dkr.ecr.us-east-1.amazonaws.com/buildify:$IMAGE_TAG|g" k8s/deployment.yaml
|
||||
# For main branch, use short SHA for immutable deployments
|
||||
IMAGE_TAG="sha-${GITHUB_SHA:0:7}"
|
||||
elif [[ "${{ github.ref }}" == "refs/heads/dev" ]]; then
|
||||
# For dev branch, use 'dev'
|
||||
# No need to change as dev is already set as the default tag in deployment.yaml
|
||||
IMAGE_TAG="dev"
|
||||
echo "Using dev tag for dev branch - no change needed to deployment.yaml"
|
||||
# For dev branch, use short SHA for immutable deployments
|
||||
IMAGE_TAG="sha-${GITHUB_SHA:0:7}"
|
||||
else
|
||||
# For other branches/PRs, use the short SHA
|
||||
IMAGE_TAG="${GITHUB_SHA:0:7}"
|
||||
sed -i "s|891377135844.dkr.ecr.us-east-1.amazonaws.com/buildify:dev|891377135844.dkr.ecr.us-east-1.amazonaws.com/buildify:$IMAGE_TAG|g" k8s/deployment.yaml
|
||||
IMAGE_TAG="sha-${GITHUB_SHA:0:7}"
|
||||
fi
|
||||
|
||||
# Update deployment file with new image tag
|
||||
# Use a more flexible pattern to match any existing tag
|
||||
sed -i "s|891377135844\.dkr\.ecr\.us-east-1\.amazonaws\.com/buildify:[^[:space:]]*|891377135844.dkr.ecr.us-east-1.amazonaws.com/buildify:$IMAGE_TAG|g" k8s/deployment.yaml
|
||||
|
||||
# Show changes for logging
|
||||
echo "Modified deployment manifest:"
|
||||
cat k8s/deployment.yaml
|
||||
@ -220,10 +219,13 @@ jobs:
|
||||
git push origin $BRANCH_NAME
|
||||
|
||||
# Create PR
|
||||
PR_TITLE="Update Kubernetes deployment to new image"
|
||||
BRANCH_TYPE=$(echo "${{ github.ref }}" | sed 's|refs/heads/||')
|
||||
PR_TITLE="Update Kubernetes deployment to new image (${BRANCH_TYPE})"
|
||||
PR_BODY="This PR updates the Kubernetes deployment to use the latest image built from commit ${GITHUB_SHA}.
|
||||
|
||||
Image: ${{ steps.ecr-registry.outputs.registry }}:$IMAGE_TAG
|
||||
**Branch:** ${BRANCH_TYPE}
|
||||
**Commit:** ${GITHUB_SHA}
|
||||
**Image:** ${{ steps.ecr-registry.outputs.registry }}:$IMAGE_TAG
|
||||
|
||||
This PR was automatically generated by the CI/CD pipeline."
|
||||
|
||||
|
@ -44,41 +44,99 @@ export class AuthError extends Error {
|
||||
}
|
||||
}
|
||||
|
||||
// Get environment variables
|
||||
const clientId = process.env.GITHUB_CLIENT_ID;
|
||||
const clientSecret = process.env.GITHUB_CLIENT_SECRET;
|
||||
const sessionSecret = process.env.SESSION_SECRET || 'buildify-session-secret';
|
||||
// Global environment variable cache to ensure consistency across context availability
|
||||
let globalEnvCache: { [key: string]: string } = {};
|
||||
|
||||
// Validate required environment variables
|
||||
if (!clientId || !clientSecret) {
|
||||
console.warn(
|
||||
'GitHub OAuth is not configured properly. Please set GITHUB_CLIENT_ID and GITHUB_CLIENT_SECRET environment variables.'
|
||||
);
|
||||
// Helper function to get environment variables from context or process.env
|
||||
function getEnvVar(context: any, key: string, defaultValue: string = ''): string {
|
||||
// Use cached global value if available
|
||||
if (globalEnvCache[key]) {
|
||||
return globalEnvCache[key];
|
||||
}
|
||||
|
||||
let value = '';
|
||||
|
||||
// Try context.cloudflare.env first (Cloudflare Workers with wrangler bindings)
|
||||
if (context?.cloudflare?.env?.[key]) {
|
||||
value = context.cloudflare.env[key];
|
||||
}
|
||||
// Try context.env (other Cloudflare Workers setups)
|
||||
else if (context?.env?.[key]) {
|
||||
value = context.env[key];
|
||||
}
|
||||
// Fallback to process.env (Node.js)
|
||||
else {
|
||||
value = process.env[key] || defaultValue;
|
||||
}
|
||||
|
||||
// Cache environment variables globally for consistency
|
||||
if (value && !globalEnvCache[key]) {
|
||||
globalEnvCache[key] = value;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
// Create OAuth app instance
|
||||
export const oauthApp = new OAuthApp({
|
||||
clientId: clientId || '',
|
||||
clientSecret: clientSecret || '',
|
||||
defaultScopes: ['read:user', 'user:email'],
|
||||
});
|
||||
// Create OAuth app instance with environment variables
|
||||
function createOAuthApp(context?: any) {
|
||||
const clientId = getEnvVar(context, 'GITHUB_CLIENT_ID');
|
||||
const clientSecret = getEnvVar(context, 'GITHUB_CLIENT_SECRET');
|
||||
|
||||
|
||||
if (!clientId || !clientSecret) {
|
||||
console.warn(
|
||||
'GitHub OAuth is not configured properly. Please set GITHUB_CLIENT_ID and GITHUB_CLIENT_SECRET environment variables.'
|
||||
);
|
||||
}
|
||||
|
||||
// Create session storage
|
||||
const sessionStorage = createCookieSessionStorage({
|
||||
cookie: {
|
||||
name: 'buildify_auth_session',
|
||||
httpOnly: true,
|
||||
path: '/',
|
||||
sameSite: 'lax',
|
||||
secrets: [sessionSecret],
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
},
|
||||
});
|
||||
return new OAuthApp({
|
||||
clientId: clientId || '',
|
||||
clientSecret: clientSecret || '',
|
||||
defaultScopes: ['read:user', 'user:email'],
|
||||
});
|
||||
}
|
||||
|
||||
// Create session storage with context-aware secrets
|
||||
function createSessionStorage(context?: any, request?: Request) {
|
||||
const sessionSecret = getEnvVar(context, 'SESSION_SECRET', 'buildify-session-secret');
|
||||
|
||||
// Determine if we should use secure cookies based on environment and protocol
|
||||
let secure = false;
|
||||
|
||||
// Check if we're in production environment
|
||||
const isProduction = process.env.NODE_ENV === 'production';
|
||||
|
||||
if (isProduction) {
|
||||
// In production, check for HTTPS indicators
|
||||
if (request) {
|
||||
const proto = request.headers.get('x-forwarded-proto');
|
||||
const host = request.headers.get('host');
|
||||
// Use secure cookies if we have HTTPS indicators or production domain
|
||||
secure = proto === 'https' || host?.includes('phexhub-np.int.bayer.com') || false;
|
||||
} else {
|
||||
// Default to secure in production if no request context
|
||||
secure = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return createCookieSessionStorage({
|
||||
cookie: {
|
||||
name: 'buildify_auth_session',
|
||||
httpOnly: true,
|
||||
path: '/',
|
||||
sameSite: 'lax',
|
||||
secrets: [sessionSecret],
|
||||
secure,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Get session from request
|
||||
export async function getSession(request: Request) {
|
||||
export async function getSession(request: Request, context?: any) {
|
||||
const cookie = request.headers.get('Cookie');
|
||||
return sessionStorage.getSession(cookie);
|
||||
const storage = createSessionStorage(context, request);
|
||||
return storage.getSession(cookie);
|
||||
}
|
||||
|
||||
// Generate state parameter for OAuth flow to prevent CSRF attacks
|
||||
@ -87,28 +145,34 @@ export function generateState() {
|
||||
}
|
||||
|
||||
// Store state in session
|
||||
export async function storeState(request: Request, state: string) {
|
||||
const session = await getSession(request);
|
||||
export async function storeState(request: Request, state: string, context?: any) {
|
||||
const session = await getSession(request, context);
|
||||
session.set('oauth:state', state);
|
||||
return sessionStorage.commitSession(session);
|
||||
|
||||
|
||||
const storage = createSessionStorage(context, request);
|
||||
return storage.commitSession(session);
|
||||
}
|
||||
|
||||
// Verify state from session
|
||||
export async function verifyState(request: Request, state: string) {
|
||||
const session = await getSession(request);
|
||||
export async function verifyState(request: Request, state: string, context?: any) {
|
||||
const session = await getSession(request, context);
|
||||
const storedState = session.get('oauth:state');
|
||||
|
||||
|
||||
if (!storedState || storedState !== state) {
|
||||
throw new AuthError('Invalid state parameter. Possible CSRF attack.');
|
||||
throw new AuthError(`Invalid state parameter. Expected: ${storedState}, Got: ${state}`);
|
||||
}
|
||||
|
||||
// Clear the state after verification
|
||||
session.unset('oauth:state');
|
||||
return sessionStorage.commitSession(session);
|
||||
const storage = createSessionStorage(context, request);
|
||||
return storage.commitSession(session);
|
||||
}
|
||||
|
||||
// Generate authorization URL
|
||||
export function getAuthorizationUrl(state: string, redirectUri?: string) {
|
||||
export function getAuthorizationUrl(state: string, redirectUri?: string, context?: any) {
|
||||
const oauthApp = createOAuthApp(context);
|
||||
return oauthApp.getWebFlowAuthorizationUrl({
|
||||
state,
|
||||
redirectUrl: redirectUri,
|
||||
@ -116,18 +180,23 @@ export function getAuthorizationUrl(state: string, redirectUri?: string) {
|
||||
}
|
||||
|
||||
// Exchange code for token
|
||||
export async function exchangeCodeForToken(code: string, state: string) {
|
||||
export async function exchangeCodeForToken(code: string, state: string, context?: any) {
|
||||
try {
|
||||
console.log('🔑 Token exchange: Starting with code length:', code?.length);
|
||||
const oauthApp = createOAuthApp(context);
|
||||
console.log('🔑 Token exchange: OAuth app created');
|
||||
|
||||
const { authentication } = await oauthApp.createToken({
|
||||
code,
|
||||
state,
|
||||
});
|
||||
console.log('🔑 Token exchange: GitHub responded successfully');
|
||||
|
||||
return {
|
||||
accessToken: authentication.token,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error exchanging code for token:', error);
|
||||
console.error('🔑 Token exchange: Failed -', error);
|
||||
throw new AuthError('Failed to exchange code for token');
|
||||
}
|
||||
}
|
||||
@ -135,18 +204,24 @@ export async function exchangeCodeForToken(code: string, state: string) {
|
||||
// Fetch user data from GitHub API
|
||||
export async function fetchGitHubUser(accessToken: string): Promise<GitHubUser> {
|
||||
try {
|
||||
console.log('👤 User fetch: Starting with token length:', accessToken?.length);
|
||||
const response = await fetch('https://api.github.com/user', {
|
||||
headers: {
|
||||
Accept: 'application/vnd.github.v3+json',
|
||||
Authorization: `token ${accessToken}`,
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
'User-Agent': 'Buildify-App/1.0',
|
||||
},
|
||||
});
|
||||
console.log('👤 User fetch: GitHub API response status:', response.status);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`GitHub API responded with ${response.status}`);
|
||||
const errorText = await response.text();
|
||||
console.error('👤 User fetch: GitHub API error response:', errorText);
|
||||
throw new Error(`GitHub API responded with ${response.status}: ${errorText}`);
|
||||
}
|
||||
|
||||
const userData = await response.json() as GitHubUserResponse;
|
||||
console.log('👤 User fetch: User data received for:', userData.login);
|
||||
|
||||
// If email is not public, try to get primary email
|
||||
let email = userData.email;
|
||||
@ -154,7 +229,8 @@ export async function fetchGitHubUser(accessToken: string): Promise<GitHubUser>
|
||||
const emailsResponse = await fetch('https://api.github.com/user/emails', {
|
||||
headers: {
|
||||
Accept: 'application/vnd.github.v3+json',
|
||||
Authorization: `token ${accessToken}`,
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
'User-Agent': 'Buildify-App/1.0',
|
||||
},
|
||||
});
|
||||
|
||||
@ -165,7 +241,7 @@ export async function fetchGitHubUser(accessToken: string): Promise<GitHubUser>
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
const user = {
|
||||
id: userData.id,
|
||||
login: userData.login,
|
||||
name: userData.name,
|
||||
@ -173,69 +249,88 @@ export async function fetchGitHubUser(accessToken: string): Promise<GitHubUser>
|
||||
avatar_url: userData.avatar_url,
|
||||
html_url: userData.html_url,
|
||||
};
|
||||
console.log('👤 User fetch: Successfully processed user data');
|
||||
return user;
|
||||
} catch (error) {
|
||||
console.error('Error fetching GitHub user:', error);
|
||||
console.error('👤 User fetch: Failed -', error);
|
||||
throw new AuthError('Failed to fetch user data from GitHub');
|
||||
}
|
||||
}
|
||||
|
||||
// Create user session
|
||||
export async function createUserSession(request: Request, authSession: AuthSession, redirectTo: string) {
|
||||
const session = await getSession(request);
|
||||
|
||||
// Store user data in session
|
||||
session.set('auth:user', authSession.user);
|
||||
session.set('auth:accessToken', authSession.accessToken);
|
||||
|
||||
// Commit session and redirect
|
||||
return redirect(redirectTo, {
|
||||
headers: {
|
||||
'Set-Cookie': await sessionStorage.commitSession(session, {
|
||||
maxAge: 60 * 60 * 24 * 7, // 1 week
|
||||
}),
|
||||
},
|
||||
});
|
||||
export async function createUserSession(request: Request, authSession: AuthSession, redirectTo: string, context?: any) {
|
||||
try {
|
||||
console.log('💾 Session create: Starting for user:', authSession.user.login);
|
||||
const session = await getSession(request, context);
|
||||
console.log('💾 Session create: Session retrieved');
|
||||
|
||||
// Store user data in session
|
||||
session.set('auth:user', authSession.user);
|
||||
session.set('auth:accessToken', authSession.accessToken);
|
||||
console.log('💾 Session create: User data stored in session');
|
||||
|
||||
// Commit session and redirect
|
||||
const storage = createSessionStorage(context, request);
|
||||
console.log('💾 Session create: Session storage created');
|
||||
|
||||
const result = redirect(redirectTo, {
|
||||
headers: {
|
||||
'Set-Cookie': await storage.commitSession(session, {
|
||||
maxAge: 60 * 60 * 24 * 7, // 1 week
|
||||
}),
|
||||
},
|
||||
});
|
||||
console.log('💾 Session create: Redirect prepared to:', redirectTo);
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error('💾 Session create: Failed -', error);
|
||||
throw new AuthError('Failed to create user session');
|
||||
}
|
||||
}
|
||||
|
||||
// Get user from session
|
||||
export async function getUserFromSession(request: Request): Promise<GitHubUser | null> {
|
||||
const session = await getSession(request);
|
||||
export async function getUserFromSession(request: Request, context?: any): Promise<GitHubUser | null> {
|
||||
const session = await getSession(request, context);
|
||||
const user = session.get('auth:user');
|
||||
return user || null;
|
||||
}
|
||||
|
||||
// Get access token from session
|
||||
export async function getAccessToken(request: Request): Promise<string | null> {
|
||||
const session = await getSession(request);
|
||||
export async function getAccessToken(request: Request, context?: any): Promise<string | null> {
|
||||
const session = await getSession(request, context);
|
||||
const accessToken = session.get('auth:accessToken');
|
||||
return accessToken || null;
|
||||
}
|
||||
|
||||
// Check if user is authenticated
|
||||
export async function isAuthenticated(request: Request): Promise<boolean> {
|
||||
const user = await getUserFromSession(request);
|
||||
return user !== null;
|
||||
export async function isAuthenticated(request: Request, context?: any): Promise<boolean> {
|
||||
const user = await getUserFromSession(request, context);
|
||||
const result = user !== null;
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Require authentication
|
||||
export async function requireAuthentication(request: Request, redirectTo: string = '/login') {
|
||||
const authenticated = await isAuthenticated(request);
|
||||
export async function requireAuthentication(request: Request, redirectTo: string = '/login', context?: any) {
|
||||
const authenticated = await isAuthenticated(request, context);
|
||||
|
||||
if (!authenticated) {
|
||||
const searchParams = new URLSearchParams([['redirectTo', request.url]]);
|
||||
throw redirect(`${redirectTo}?${searchParams}`);
|
||||
}
|
||||
|
||||
return await getUserFromSession(request);
|
||||
return await getUserFromSession(request, context);
|
||||
}
|
||||
|
||||
// Logout - destroy session
|
||||
export async function logout(request: Request, redirectTo: string = '/') {
|
||||
const session = await getSession(request);
|
||||
export async function logout(request: Request, redirectTo: string = '/', context?: any) {
|
||||
const session = await getSession(request, context);
|
||||
|
||||
const storage = createSessionStorage(context, request);
|
||||
return redirect(redirectTo, {
|
||||
headers: {
|
||||
'Set-Cookie': await sessionStorage.destroySession(session),
|
||||
'Set-Cookie': await storage.destroySession(session),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
@ -14,9 +14,9 @@ export const meta: MetaFunction = () => {
|
||||
* Root loader that checks authentication status
|
||||
* If user is not authenticated, redirect to the login page
|
||||
*/
|
||||
export async function loader({ request }: LoaderFunctionArgs) {
|
||||
export async function loader({ request, context }: LoaderFunctionArgs) {
|
||||
// Check if user is authenticated
|
||||
const authenticated = await isAuthenticated(request);
|
||||
const authenticated = await isAuthenticated(request, context);
|
||||
|
||||
// If not authenticated, redirect to login page
|
||||
if (!authenticated) {
|
||||
|
@ -1,36 +0,0 @@
|
||||
// @ts-nocheck
|
||||
import { json, redirect, type LoaderFunctionArgs } from '@remix-run/cloudflare';
|
||||
import { createUserSession, exchangeCodeForToken, fetchGitHubUser, verifyState } from '~/lib/auth/github-oauth.server';
|
||||
|
||||
export async function loader({ request }: LoaderFunctionArgs) {
|
||||
const url = new URL(request.url);
|
||||
const code = url.searchParams.get('code');
|
||||
const state = url.searchParams.get('state');
|
||||
|
||||
// Check if code and state are present
|
||||
if (!code || !state) {
|
||||
return json({ error: 'Missing required OAuth parameters' }, { status: 400 });
|
||||
}
|
||||
|
||||
try {
|
||||
// Verify state parameter to prevent CSRF attacks
|
||||
const cookie = await verifyState(request, state);
|
||||
|
||||
// Exchange code for access token
|
||||
const { accessToken } = await exchangeCodeForToken(code, state);
|
||||
|
||||
// Fetch user data from GitHub API
|
||||
const user = await fetchGitHubUser(accessToken);
|
||||
|
||||
// Create user session and redirect to home
|
||||
return createUserSession(
|
||||
request,
|
||||
{ user, accessToken },
|
||||
'/', // Redirect to home page after successful login
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Authentication error:', error);
|
||||
|
||||
return redirect('/login?error=authentication_failed');
|
||||
}
|
||||
}
|
@ -16,7 +16,7 @@ export const meta: MetaFunction = () => {
|
||||
];
|
||||
};
|
||||
|
||||
export async function loader({ request }: LoaderFunctionArgs) {
|
||||
export async function loader({ request, context }: LoaderFunctionArgs) {
|
||||
// Get URL parameters
|
||||
const url = new URL(request.url);
|
||||
const code = url.searchParams.get('code');
|
||||
@ -39,35 +39,51 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
||||
|
||||
// Validate required parameters
|
||||
if (!code || !state) {
|
||||
return json({
|
||||
success: false,
|
||||
error: 'invalid_request',
|
||||
errorDescription: 'Missing required parameters',
|
||||
redirectTo: '/login',
|
||||
});
|
||||
const errorParams = new URLSearchParams([
|
||||
['error', 'invalid_request'],
|
||||
['error_description', 'Missing required parameters']
|
||||
]);
|
||||
return redirect(`/auth/login?${errorParams}`);
|
||||
}
|
||||
|
||||
try {
|
||||
console.log('=== CALLBACK FLOW DEBUG ===');
|
||||
console.log('Step 1: Starting state verification...');
|
||||
|
||||
// Verify state parameter to prevent CSRF attacks
|
||||
const cookieHeader = await verifyState(request, state);
|
||||
const cookieHeader = await verifyState(request, state, context);
|
||||
console.log('Step 1: ✅ State verification successful');
|
||||
|
||||
console.log('Step 2: Starting token exchange...');
|
||||
// Exchange authorization code for access token
|
||||
const { accessToken } = await exchangeCodeForToken(code, state);
|
||||
const { accessToken } = await exchangeCodeForToken(code, state, context);
|
||||
console.log('Step 2: ✅ Token exchange successful');
|
||||
|
||||
console.log('Step 3: Starting user data fetch...');
|
||||
// Fetch user data from GitHub API
|
||||
const user = await fetchGitHubUser(accessToken);
|
||||
console.log('Step 3: ✅ User data fetch successful');
|
||||
|
||||
console.log('Step 4: Creating user session...');
|
||||
// Create user session and redirect
|
||||
return createUserSession(
|
||||
const result = await createUserSession(
|
||||
request,
|
||||
{
|
||||
accessToken,
|
||||
user,
|
||||
},
|
||||
redirectTo
|
||||
redirectTo,
|
||||
context
|
||||
);
|
||||
console.log('Step 4: ✅ User session created successfully');
|
||||
console.log('============================');
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error('Authentication error:', error);
|
||||
console.error('=== AUTHENTICATION FAILURE ===');
|
||||
console.error('Error type:', error.constructor.name);
|
||||
console.error('Error message:', error.message);
|
||||
console.error('Error stack:', error.stack);
|
||||
console.error('===============================');
|
||||
|
||||
let errorMessage = 'An unexpected error occurred during authentication';
|
||||
let errorCode = 'server_error';
|
||||
@ -77,12 +93,13 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
||||
errorCode = 'auth_error';
|
||||
}
|
||||
|
||||
return json({
|
||||
success: false,
|
||||
error: errorCode,
|
||||
errorDescription: errorMessage,
|
||||
redirectTo: '/login',
|
||||
});
|
||||
// For authentication failures, redirect to login with error params
|
||||
const errorParams = new URLSearchParams([
|
||||
['error', errorCode],
|
||||
['error_description', errorMessage]
|
||||
]);
|
||||
|
||||
return redirect(`/auth/login?${errorParams}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -17,9 +17,9 @@ export const meta: MetaFunction = () => {
|
||||
];
|
||||
};
|
||||
|
||||
export async function loader({ request }: LoaderFunctionArgs) {
|
||||
export async function loader({ request, context }: LoaderFunctionArgs) {
|
||||
// Check if user is already authenticated
|
||||
const authenticated = await isAuthenticated(request);
|
||||
const authenticated = await isAuthenticated(request, context);
|
||||
if (authenticated) {
|
||||
// Redirect to home page if already logged in
|
||||
return redirect('/');
|
||||
@ -28,25 +28,35 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
||||
// Get redirect URL from query params (if any)
|
||||
const url = new URL(request.url);
|
||||
const redirectTo = url.searchParams.get('redirectTo') || '/';
|
||||
|
||||
// Check for authentication error parameters
|
||||
const authError = url.searchParams.get('error');
|
||||
const authErrorDescription = url.searchParams.get('error_description');
|
||||
|
||||
// Check if GitHub OAuth is configured
|
||||
const clientId = process.env.GITHUB_CLIENT_ID;
|
||||
const clientSecret = process.env.GITHUB_CLIENT_SECRET;
|
||||
// Check if GitHub OAuth is configured using context
|
||||
const clientId = (context?.cloudflare?.env?.GITHUB_CLIENT_ID) || (context?.env?.GITHUB_CLIENT_ID) || process.env.GITHUB_CLIENT_ID;
|
||||
const clientSecret = (context?.cloudflare?.env?.GITHUB_CLIENT_SECRET) || (context?.env?.GITHUB_CLIENT_SECRET) || process.env.GITHUB_CLIENT_SECRET;
|
||||
const isConfigured = Boolean(clientId && clientSecret);
|
||||
|
||||
// Determine the error message to display
|
||||
let errorMessage = null;
|
||||
if (authError) {
|
||||
errorMessage = authErrorDescription || 'Authentication failed. Please try again.';
|
||||
} else if (!isConfigured) {
|
||||
errorMessage = 'GitHub OAuth is not configured. Please set GITHUB_CLIENT_ID and GITHUB_CLIENT_SECRET environment variables.';
|
||||
}
|
||||
|
||||
return json({
|
||||
redirectTo,
|
||||
isConfigured,
|
||||
error: isConfigured
|
||||
? null
|
||||
: 'GitHub OAuth is not configured. Please set GITHUB_CLIENT_ID and GITHUB_CLIENT_SECRET environment variables.',
|
||||
error: errorMessage,
|
||||
});
|
||||
}
|
||||
|
||||
export async function action({ request }: ActionFunctionArgs) {
|
||||
// Check if GitHub OAuth is configured
|
||||
const clientId = process.env.GITHUB_CLIENT_ID;
|
||||
const clientSecret = process.env.GITHUB_CLIENT_SECRET;
|
||||
export async function action({ request, context }: ActionFunctionArgs) {
|
||||
// Check if GitHub OAuth is configured using context
|
||||
const clientId = (context?.cloudflare?.env?.GITHUB_CLIENT_ID) || (context?.env?.GITHUB_CLIENT_ID) || process.env.GITHUB_CLIENT_ID;
|
||||
const clientSecret = (context?.cloudflare?.env?.GITHUB_CLIENT_SECRET) || (context?.env?.GITHUB_CLIENT_SECRET) || process.env.GITHUB_CLIENT_SECRET;
|
||||
|
||||
if (!clientId || !clientSecret) {
|
||||
return json(
|
||||
@ -71,10 +81,10 @@ export async function action({ request }: ActionFunctionArgs) {
|
||||
const callbackUrl = `${url.origin}/auth/callback`;
|
||||
|
||||
// Generate authorization URL
|
||||
const { url: authorizationUrl } = getAuthorizationUrl(state, callbackUrl);
|
||||
const { url: authorizationUrl } = getAuthorizationUrl(state, callbackUrl, context);
|
||||
|
||||
// Store state in session
|
||||
const cookie = await storeState(request, state);
|
||||
const cookie = await storeState(request, state, context);
|
||||
|
||||
// Redirect to GitHub authorization URL
|
||||
return redirect(authorizationUrl, {
|
||||
|
@ -12,13 +12,13 @@ export const meta: MetaFunction = () => {
|
||||
];
|
||||
};
|
||||
|
||||
export async function loader({ request }: LoaderFunctionArgs) {
|
||||
export async function loader({ request, context }: LoaderFunctionArgs) {
|
||||
// Get URL parameters
|
||||
const url = new URL(request.url);
|
||||
const redirectTo = url.searchParams.get('redirectTo') || '/';
|
||||
|
||||
// Get user data to display in the confirmation page
|
||||
const user = await getUserFromSession(request);
|
||||
const user = await getUserFromSession(request, context);
|
||||
|
||||
return json({
|
||||
redirectTo,
|
||||
@ -26,13 +26,13 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
||||
});
|
||||
}
|
||||
|
||||
export async function action({ request }: ActionFunctionArgs) {
|
||||
export async function action({ request, context }: ActionFunctionArgs) {
|
||||
// Get form data
|
||||
const formData = await request.formData();
|
||||
const redirectTo = formData.get('redirectTo')?.toString() || '/';
|
||||
|
||||
// Perform logout and redirect
|
||||
return logout(request, redirectTo);
|
||||
return logout(request, redirectTo, context);
|
||||
}
|
||||
|
||||
export default function Logout() {
|
||||
|
@ -27,6 +27,10 @@ services:
|
||||
- VITE_LOG_LEVEL=${VITE_LOG_LEVEL:-debug}
|
||||
- DEFAULT_NUM_CTX=${DEFAULT_NUM_CTX:-32768}
|
||||
- RUNNING_IN_DOCKER=true
|
||||
# GitHub OAuth environment variables
|
||||
- GITHUB_CLIENT_ID=${GITHUB_CLIENT_ID}
|
||||
- GITHUB_CLIENT_SECRET=${GITHUB_CLIENT_SECRET}
|
||||
- SESSION_SECRET=${SESSION_SECRET}
|
||||
extra_hosts:
|
||||
- 'host.docker.internal:host-gateway'
|
||||
command: pnpm run dockerstart
|
||||
@ -60,6 +64,10 @@ services:
|
||||
- VITE_LOG_LEVEL=${VITE_LOG_LEVEL:-debug}
|
||||
- DEFAULT_NUM_CTX=${DEFAULT_NUM_CTX:-32768}
|
||||
- RUNNING_IN_DOCKER=true
|
||||
# GitHub OAuth environment variables
|
||||
- GITHUB_CLIENT_ID=${GITHUB_CLIENT_ID}
|
||||
- GITHUB_CLIENT_SECRET=${GITHUB_CLIENT_SECRET}
|
||||
- SESSION_SECRET=${SESSION_SECRET}
|
||||
extra_hosts:
|
||||
- 'host.docker.internal:host-gateway'
|
||||
volumes:
|
||||
|
@ -5,5 +5,6 @@ metadata:
|
||||
namespace: buildify
|
||||
data:
|
||||
RUNNING_IN_DOCKER: "true"
|
||||
NODE_ENV: "production"
|
||||
VITE_LOG_LEVEL: "info"
|
||||
# Add other non-sensitive configuration here
|
@ -23,7 +23,7 @@ spec:
|
||||
serviceAccountName: ecr-service-account
|
||||
containers:
|
||||
- name: buildify
|
||||
image: 891377135844.dkr.ecr.us-east-1.amazonaws.com/buildify:development
|
||||
image: 891377135844.dkr.ecr.us-east-1.amazonaws.com/buildify:sha-placeholder
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- containerPort: 5173
|
||||
|
3
worker-configuration.d.ts
vendored
3
worker-configuration.d.ts
vendored
@ -18,4 +18,7 @@ interface Env {
|
||||
XAI_API_KEY: string;
|
||||
PERPLEXITY_API_KEY: string;
|
||||
AWS_BEDROCK_CONFIG: string;
|
||||
GITHUB_CLIENT_ID: string;
|
||||
GITHUB_CLIENT_SECRET: string;
|
||||
SESSION_SECRET: string;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user