drbh commited on
Commit
151f8f5
Β·
1 Parent(s): e30ea26

fix: adjust auth flow

Browse files
app/lib/github-app.server.ts CHANGED
@@ -1,6 +1,7 @@
1
  import { App } from "@octokit/app";
2
  import { createAppAuth } from "@octokit/auth-app";
3
  import jwt from "jsonwebtoken";
 
4
 
5
  // GitHub App configuration - these should be environment variables in production
6
  const GITHUB_APP_ID = process.env.GITHUB_APP_ID;
@@ -9,9 +10,21 @@ const GITHUB_APP_CLIENT_ID = process.env.GITHUB_APP_CLIENT_ID;
9
  const GITHUB_APP_CLIENT_SECRET = process.env.GITHUB_APP_CLIENT_SECRET;
10
 
11
  if (!GITHUB_APP_ID || !GITHUB_APP_PRIVATE_KEY || !GITHUB_APP_CLIENT_ID || !GITHUB_APP_CLIENT_SECRET) {
 
 
 
 
 
12
  throw new Error('Missing required GitHub App environment variables. Please check your .env file.');
13
  }
14
 
 
 
 
 
 
 
 
15
  // For now, we'll hardcode a simple in-memory store
16
  // In production, you'd use a database
17
  const userAuthStore = new Map<string, any>();
@@ -58,6 +71,9 @@ export class GitHubAppAuth {
58
  throw new Error('GITHUB_CALLBACK_URL environment variable is required');
59
  }
60
 
 
 
 
61
  const params = new URLSearchParams({
62
  client_id: GITHUB_APP_CLIENT_ID,
63
  redirect_uri: callbackUrl,
@@ -65,7 +81,10 @@ export class GitHubAppAuth {
65
  state: state || '',
66
  });
67
 
68
- return `https://github.com/login/oauth/authorize?${params.toString()}`;
 
 
 
69
  }
70
 
71
  /**
@@ -73,11 +92,15 @@ export class GitHubAppAuth {
73
  */
74
  async handleCallback(code: string, state?: string) {
75
  try {
 
 
 
76
  const { data } = await this.app.oauth.createToken({
77
  code,
78
  });
79
 
80
  const { token } = data;
 
81
 
82
  // Get user information
83
  const userOctokit = await this.app.oauth.getUserOctokit({
@@ -85,6 +108,7 @@ export class GitHubAppAuth {
85
  });
86
 
87
  const { data: user } = await userOctokit.rest.users.getAuthenticated();
 
88
 
89
  // Store user auth info (in production, save to database)
90
  const userAuth = {
@@ -99,10 +123,26 @@ export class GitHubAppAuth {
99
  };
100
 
101
  userAuthStore.set(user.login, userAuth);
 
102
 
103
  return userAuth;
104
- } catch (error) {
105
- console.error('GitHub callback error:', error);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
106
  throw new Error('Failed to authenticate with GitHub');
107
  }
108
  }
@@ -152,12 +192,16 @@ export class GitHubAppAuth {
152
  return true;
153
  }
154
 
155
- const expectedSignature = `sha256=${require('crypto')
156
- .createHmac('sha256', webhookSecret)
157
- .update(payload, 'utf8')
158
- .digest('hex')}`;
159
 
160
- return signature === expectedSignature;
 
 
 
 
161
  }
162
  }
163
 
 
1
  import { App } from "@octokit/app";
2
  import { createAppAuth } from "@octokit/auth-app";
3
  import jwt from "jsonwebtoken";
4
+ import { createHmac } from "crypto";
5
 
6
  // GitHub App configuration - these should be environment variables in production
7
  const GITHUB_APP_ID = process.env.GITHUB_APP_ID;
 
10
  const GITHUB_APP_CLIENT_SECRET = process.env.GITHUB_APP_CLIENT_SECRET;
11
 
12
  if (!GITHUB_APP_ID || !GITHUB_APP_PRIVATE_KEY || !GITHUB_APP_CLIENT_ID || !GITHUB_APP_CLIENT_SECRET) {
13
+ console.error('❌ Missing required GitHub App environment variables:');
14
+ console.error('- GITHUB_APP_ID:', GITHUB_APP_ID ? 'βœ… Set' : '❌ Missing');
15
+ console.error('- GITHUB_APP_PRIVATE_KEY:', GITHUB_APP_PRIVATE_KEY ? 'βœ… Set' : '❌ Missing');
16
+ console.error('- GITHUB_APP_CLIENT_ID:', GITHUB_APP_CLIENT_ID ? 'βœ… Set' : '❌ Missing');
17
+ console.error('- GITHUB_APP_CLIENT_SECRET:', GITHUB_APP_CLIENT_SECRET ? 'βœ… Set' : '❌ Missing');
18
  throw new Error('Missing required GitHub App environment variables. Please check your .env file.');
19
  }
20
 
21
+ // Log startup info (safe - no secrets)
22
+ console.log('πŸš€ GitHub App Configuration:');
23
+ console.log('- App ID:', GITHUB_APP_ID);
24
+ console.log('- Client ID:', GITHUB_APP_CLIENT_ID);
25
+ console.log('- App Name:', process.env.GITHUB_APP_NAME);
26
+ console.log('- Callback URL:', process.env.GITHUB_CALLBACK_URL);
27
+
28
  // For now, we'll hardcode a simple in-memory store
29
  // In production, you'd use a database
30
  const userAuthStore = new Map<string, any>();
 
71
  throw new Error('GITHUB_CALLBACK_URL environment variable is required');
72
  }
73
 
74
+ console.log('πŸ”— Generating OAuth URL with client ID:', GITHUB_APP_CLIENT_ID);
75
+ console.log('πŸ”— Callback URL:', callbackUrl);
76
+
77
  const params = new URLSearchParams({
78
  client_id: GITHUB_APP_CLIENT_ID,
79
  redirect_uri: callbackUrl,
 
81
  state: state || '',
82
  });
83
 
84
+ const url = `https://github.com/login/oauth/authorize?${params.toString()}`;
85
+ console.log('πŸ”— Generated OAuth URL:', url);
86
+
87
+ return url;
88
  }
89
 
90
  /**
 
92
  */
93
  async handleCallback(code: string, state?: string) {
94
  try {
95
+ console.log('πŸ”„ Starting OAuth callback with code:', code.substring(0, 8) + '...');
96
+ console.log('πŸ”„ Using client ID:', GITHUB_APP_CLIENT_ID);
97
+
98
  const { data } = await this.app.oauth.createToken({
99
  code,
100
  });
101
 
102
  const { token } = data;
103
+ console.log('βœ… Successfully obtained OAuth token');
104
 
105
  // Get user information
106
  const userOctokit = await this.app.oauth.getUserOctokit({
 
108
  });
109
 
110
  const { data: user } = await userOctokit.rest.users.getAuthenticated();
111
+ console.log('βœ… Successfully obtained user info for:', user.login);
112
 
113
  // Store user auth info (in production, save to database)
114
  const userAuth = {
 
123
  };
124
 
125
  userAuthStore.set(user.login, userAuth);
126
+ console.log('βœ… User authentication stored for:', user.login);
127
 
128
  return userAuth;
129
+ } catch (error: any) {
130
+ console.error('❌ GitHub callback error details:');
131
+ console.error('- Error type:', error.constructor.name);
132
+ console.error('- Error message:', error.message);
133
+ console.error('- Error status:', error.status);
134
+
135
+ if (error.request) {
136
+ console.error('- Request details:');
137
+ console.error(' - URL:', error.request.url);
138
+ console.error(' - Method:', error.request.method);
139
+ console.error(' - Client ID used:', error.request.client_id);
140
+ }
141
+
142
+ if (error.response?.data) {
143
+ console.error('- Response data:', error.response.data);
144
+ }
145
+
146
  throw new Error('Failed to authenticate with GitHub');
147
  }
148
  }
 
192
  return true;
193
  }
194
 
195
+ try {
196
+ const expectedSignature = `sha256=${createHmac('sha256', webhookSecret)
197
+ .update(payload, 'utf8')
198
+ .digest('hex')}`;
199
 
200
+ return signature === expectedSignature;
201
+ } catch (error) {
202
+ console.error('Error verifying webhook signature:', error);
203
+ return false;
204
+ }
205
  }
206
  }
207
 
app/routes/_index.tsx CHANGED
@@ -14,12 +14,14 @@ export async function loader({ request }: LoaderFunctionArgs) {
14
  const user = await getUserSession(request);
15
  const url = new URL(request.url);
16
  const error = url.searchParams.get("error");
 
 
17
 
18
- return json({ user, error });
19
  }
20
 
21
  export default function Index() {
22
- const { user, error } = useLoaderData<typeof loader>();
23
  const [searchParams] = useSearchParams();
24
 
25
  return (
@@ -58,9 +60,31 @@ export default function Index() {
58
  Authentication Error
59
  </h3>
60
  <div className="mt-2 text-sm text-red-700">
61
- {error === "oauth_failed" && "OAuth authentication failed. Please try again."}
 
 
 
 
 
62
  {error === "no_code" && "No authorization code received from GitHub."}
63
- {error === "callback_failed" && "Failed to complete authentication. Please try again."}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
  </div>
65
  </div>
66
  </div>
 
14
  const user = await getUserSession(request);
15
  const url = new URL(request.url);
16
  const error = url.searchParams.get("error");
17
+ const details = url.searchParams.get("details");
18
+ const message = url.searchParams.get("message");
19
 
20
+ return json({ user, error, details, message });
21
  }
22
 
23
  export default function Index() {
24
+ const { user, error, details, message } = useLoaderData<typeof loader>();
25
  const [searchParams] = useSearchParams();
26
 
27
  return (
 
60
  Authentication Error
61
  </h3>
62
  <div className="mt-2 text-sm text-red-700">
63
+ {error === "oauth_failed" && (
64
+ <div>
65
+ <p>OAuth authentication failed.</p>
66
+ {details && <p className="mt-1 font-mono text-xs">Details: {details}</p>}
67
+ </div>
68
+ )}
69
  {error === "no_code" && "No authorization code received from GitHub."}
70
+ {error === "callback_failed" && (
71
+ <div>
72
+ <p>Failed to complete authentication.</p>
73
+ {message && <p className="mt-1 font-mono text-xs">Error: {message}</p>}
74
+ </div>
75
+ )}
76
+ {error === "expired_code" && (
77
+ <div>
78
+ <p>The authorization code has expired or is invalid.</p>
79
+ <p className="mt-1">Please try signing in again.</p>
80
+ </div>
81
+ )}
82
+ {error === "invalid_client" && (
83
+ <div>
84
+ <p>GitHub App configuration error.</p>
85
+ <p className="mt-1">Please check the client ID and secret.</p>
86
+ </div>
87
+ )}
88
  </div>
89
  </div>
90
  </div>
app/routes/auth.github.callback.tsx CHANGED
@@ -8,11 +8,20 @@ export async function loader({ request }: LoaderFunctionArgs) {
8
  const code = url.searchParams.get("code");
9
  const state = url.searchParams.get("state");
10
  const error = url.searchParams.get("error");
 
 
 
 
 
 
 
 
 
11
 
12
  // Handle OAuth errors
13
  if (error) {
14
  console.error("GitHub OAuth error:", error);
15
- return redirect("/?error=oauth_failed");
16
  }
17
 
18
  if (!code) {
@@ -34,13 +43,25 @@ export async function loader({ request }: LoaderFunctionArgs) {
34
  avatar_url: userAuth.avatar_url,
35
  });
36
 
37
- return redirect("/dashboard", {
 
 
 
38
  headers: {
39
  "Set-Cookie": await commitSession(session),
40
  },
41
  });
42
- } catch (error) {
43
  console.error("GitHub callback error:", error);
44
- return redirect("/?error=callback_failed");
 
 
 
 
 
 
 
 
 
45
  }
46
  }
 
8
  const code = url.searchParams.get("code");
9
  const state = url.searchParams.get("state");
10
  const error = url.searchParams.get("error");
11
+ const installation_id = url.searchParams.get("installation_id");
12
+ const setup_action = url.searchParams.get("setup_action");
13
+
14
+ console.log('πŸ”„ OAuth callback received:');
15
+ console.log('- Code:', code ? code.substring(0, 8) + '...' : 'Missing');
16
+ console.log('- State:', state);
17
+ console.log('- Error:', error);
18
+ console.log('- Installation ID:', installation_id);
19
+ console.log('- Setup Action:', setup_action);
20
 
21
  // Handle OAuth errors
22
  if (error) {
23
  console.error("GitHub OAuth error:", error);
24
+ return redirect(`/?error=oauth_failed&details=${encodeURIComponent(error)}`);
25
  }
26
 
27
  if (!code) {
 
43
  avatar_url: userAuth.avatar_url,
44
  });
45
 
46
+ const redirectUrl = installation_id ? "/install" : "/dashboard";
47
+ console.log('βœ… OAuth success, redirecting to:', redirectUrl);
48
+
49
+ return redirect(redirectUrl, {
50
  headers: {
51
  "Set-Cookie": await commitSession(session),
52
  },
53
  });
54
+ } catch (error: any) {
55
  console.error("GitHub callback error:", error);
56
+
57
+ // Provide more specific error information
58
+ let errorCode = "callback_failed";
59
+ if (error.message?.includes("bad_verification_code") || error.message?.includes("incorrect or expired")) {
60
+ errorCode = "expired_code";
61
+ } else if (error.message?.includes("client_id")) {
62
+ errorCode = "invalid_client";
63
+ }
64
+
65
+ return redirect(`/?error=${errorCode}&message=${encodeURIComponent(error.message)}`);
66
  }
67
  }
app/routes/debug.tsx ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { json } from "@remix-run/node";
2
+ import type { LoaderFunctionArgs } from "@remix-run/node";
3
+
4
+ export async function loader({ request }: LoaderFunctionArgs) {
5
+ // Only allow this in development or if a special debug header is set
6
+ const isDev = process.env.NODE_ENV === "development";
7
+ const debugHeader = request.headers.get("x-debug-token");
8
+ const debugToken = process.env.DEBUG_TOKEN;
9
+
10
+ if (!isDev && (!debugToken || debugHeader !== debugToken)) {
11
+ throw new Response("Not Found", { status: 404 });
12
+ }
13
+
14
+ // Safe environment info (no secrets)
15
+ const envInfo = {
16
+ NODE_ENV: process.env.NODE_ENV,
17
+ GITHUB_APP_ID: process.env.GITHUB_APP_ID ? 'βœ… Set' : '❌ Missing',
18
+ GITHUB_APP_NAME: process.env.GITHUB_APP_NAME ? 'βœ… Set' : '❌ Missing',
19
+ GITHUB_APP_PRIVATE_KEY: process.env.GITHUB_APP_PRIVATE_KEY ? 'βœ… Set' : '❌ Missing',
20
+ GITHUB_APP_CLIENT_ID: process.env.GITHUB_APP_CLIENT_ID ? `βœ… Set (${process.env.GITHUB_APP_CLIENT_ID?.substring(0, 8)}...)` : '❌ Missing',
21
+ GITHUB_APP_CLIENT_SECRET: process.env.GITHUB_APP_CLIENT_SECRET ? 'βœ… Set' : '❌ Missing',
22
+ GITHUB_WEBHOOK_SECRET: process.env.GITHUB_WEBHOOK_SECRET ? 'βœ… Set' : '❌ Missing',
23
+ GITHUB_CALLBACK_URL: process.env.GITHUB_CALLBACK_URL || '❌ Missing',
24
+ SESSION_SECRET: process.env.SESSION_SECRET ? 'βœ… Set' : '❌ Missing',
25
+ };
26
+
27
+ return json({
28
+ timestamp: new Date().toISOString(),
29
+ environment: envInfo,
30
+ request: {
31
+ url: request.url,
32
+ method: request.method,
33
+ headers: Object.fromEntries(request.headers.entries()),
34
+ }
35
+ });
36
+ }
app/routes/webhook.github.tsx CHANGED
@@ -7,21 +7,30 @@ export async function action({ request }: ActionFunctionArgs) {
7
  return json({ error: "Method not allowed" }, { status: 405 });
8
  }
9
 
 
 
 
10
  try {
11
- const payload = await request.text();
12
  const signature = request.headers.get("x-hub-signature-256") || "";
13
  const event = request.headers.get("x-github-event") || "";
14
  const delivery = request.headers.get("x-github-delivery") || "";
15
 
 
 
16
  // Verify webhook signature
17
  if (!githubApp.verifyWebhookSignature(payload, signature)) {
18
  console.error("Invalid webhook signature");
19
  return json({ error: "Invalid signature" }, { status: 401 });
20
  }
21
 
22
- const eventData = JSON.parse(payload);
 
 
 
 
 
23
 
24
- console.log(`πŸ“₯ Received GitHub webhook: ${event} (${delivery})`);
25
  console.log("Event data:", JSON.stringify(eventData, null, 2));
26
 
27
  // Handle different webhook events
 
7
  return json({ error: "Method not allowed" }, { status: 405 });
8
  }
9
 
10
+ let payload: string;
11
+ let eventData: any;
12
+
13
  try {
14
+ payload = await request.text();
15
  const signature = request.headers.get("x-hub-signature-256") || "";
16
  const event = request.headers.get("x-github-event") || "";
17
  const delivery = request.headers.get("x-github-delivery") || "";
18
 
19
+ console.log(`πŸ“₯ Received GitHub webhook: ${event} (${delivery})`);
20
+
21
  // Verify webhook signature
22
  if (!githubApp.verifyWebhookSignature(payload, signature)) {
23
  console.error("Invalid webhook signature");
24
  return json({ error: "Invalid signature" }, { status: 401 });
25
  }
26
 
27
+ try {
28
+ eventData = JSON.parse(payload);
29
+ } catch (parseError) {
30
+ console.error("Failed to parse webhook payload:", parseError);
31
+ return json({ error: "Invalid JSON payload" }, { status: 400 });
32
+ }
33
 
 
34
  console.log("Event data:", JSON.stringify(eventData, null, 2));
35
 
36
  // Handle different webhook events