pierre-review 0.1.10 → 0.1.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/api/routes/auth.js
CHANGED
|
@@ -26,6 +26,14 @@ export async function authRoutes(app) {
|
|
|
26
26
|
// Verify state → exchange code for a token → fetch the user → upsert account →
|
|
27
27
|
// set session → 302 to the app.
|
|
28
28
|
app.get('/api/auth/callback', async (req, reply) => {
|
|
29
|
+
// Already signed in? The callback URL carries a one-time ?code= that GitHub
|
|
30
|
+
// expires in ~10 min and invalidates on first use. A back-button, a restored
|
|
31
|
+
// tab, or a Chrome profile switch can re-request this exact URL; re-running the
|
|
32
|
+
// exchange would then fail on the already-consumed code ("code incorrect or
|
|
33
|
+
// expired"). Short-circuit a valid session straight to the app instead.
|
|
34
|
+
if (req.session.get('accountId') != null) {
|
|
35
|
+
return reply.redirect('/app');
|
|
36
|
+
}
|
|
29
37
|
const { code, state } = req.query;
|
|
30
38
|
const rawCookie = req.cookies.pierre_oauth_state;
|
|
31
39
|
const unsigned = rawCookie
|
|
@@ -33,9 +41,9 @@ export async function authRoutes(app) {
|
|
|
33
41
|
: { valid: false, value: null };
|
|
34
42
|
reply.clearCookie('pierre_oauth_state', { path: '/' });
|
|
35
43
|
if (!code || !state || !unsigned.valid || unsigned.value !== state) {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
44
|
+
// Stale/replayed callback (state cookie expired or CSRF mismatch). Bounce
|
|
45
|
+
// back so the user starts a fresh sign-in — never render raw JSON to a human.
|
|
46
|
+
return reply.redirect('/app?auth=expired');
|
|
39
47
|
}
|
|
40
48
|
// Exchange the code for a user access token.
|
|
41
49
|
let token;
|
|
@@ -52,18 +60,14 @@ export async function authRoutes(app) {
|
|
|
52
60
|
});
|
|
53
61
|
const json = (await res.json());
|
|
54
62
|
if (!json.access_token) {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
message: `OAuth token exchange failed: ${json.error_description ?? json.error ?? 'no token'}`,
|
|
58
|
-
});
|
|
63
|
+
req.log.warn({ err: json.error_description ?? json.error }, 'oauth token exchange returned no token');
|
|
64
|
+
return reply.redirect('/app?auth=failed');
|
|
59
65
|
}
|
|
60
66
|
token = json.access_token;
|
|
61
67
|
}
|
|
62
68
|
catch (err) {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
message: `OAuth exchange request failed: ${err instanceof Error ? err.message : err}`,
|
|
66
|
-
});
|
|
69
|
+
req.log.error({ err }, 'oauth token exchange request failed');
|
|
70
|
+
return reply.redirect('/app?auth=error');
|
|
67
71
|
}
|
|
68
72
|
// Identify the user.
|
|
69
73
|
let user;
|
|
@@ -76,17 +80,14 @@ export async function authRoutes(app) {
|
|
|
76
80
|
},
|
|
77
81
|
});
|
|
78
82
|
if (!res.ok) {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
.send({ error: 'Unauthorized', message: 'Failed to fetch GitHub user.' });
|
|
83
|
+
req.log.warn({ status: res.status }, 'failed to fetch github user');
|
|
84
|
+
return reply.redirect('/app?auth=failed');
|
|
82
85
|
}
|
|
83
86
|
user = (await res.json());
|
|
84
87
|
}
|
|
85
88
|
catch (err) {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
message: `Fetching GitHub user failed: ${err instanceof Error ? err.message : err}`,
|
|
89
|
-
});
|
|
89
|
+
req.log.error({ err }, 'fetching github user failed');
|
|
90
|
+
return reply.redirect('/app?auth=error');
|
|
90
91
|
}
|
|
91
92
|
const account = await upsertCloudAccount({
|
|
92
93
|
githubUserId: user.node_id,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pierre-review",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.12",
|
|
4
4
|
"description": "Dashboard for tracking your team's GitHub PR activity across repos — local (SQLite + gh) or self-hosted multi-tenant cloud (Postgres + GitHub App).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "Alex Wakeman",
|