Skip to content

Commit 15d5e4e

Browse files
authored
Merge pull request #23 from oslabs-beta/lorenc-ci
Merge branch 'main' into lorenc-ci
2 parents d71ffb8 + f2598fd commit 15d5e4e

17 files changed

Lines changed: 354 additions & 494 deletions

server/auth.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,23 @@
1+
//Module for managin OAuth state tokens to prevent CSRF attacks and track redirect targets during OAuth flows
12
import crypto from 'node:crypto';
23

34
const stateStore = new Map();
45
const STATE_TTL_MS = 10 * 60 * 1000;
56

7+
// Create a random OAuth state token and store metadata
68
export function createState(redirectTo = '/') {
79
const state = crypto.randomBytes(16).toString('hex');
810
stateStore.set(state, { createdAt: Date.now(), redirectTo });
911
return state;
1012
}
1113

14+
// Validate and consume a previously generated state token
1215
export function consumeState(state) {
1316
const item = stateStore.get(state);
17+
1418
if (!item) return null;
1519
stateStore.delete(state);
20+
1621
if (Date.now() - item.createdAt > STATE_TTL_MS) return null;
1722
return item;
1823
}

server/db.js

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,19 @@
1-
// definitely periodically run Prettier or some other formatter on your codebase!
2-
31
import 'dotenv/config';
42
import pkg from 'pg';
53
const { Pool } = pkg;
64

5+
// Central Postgres connection pool for the entire backend.
6+
//
7+
// Why a pool?
8+
// - Reuses TCP connections instead of opening a new one per query.
9+
// - Limits max concurrent connections so we don’t overload the DB.
10+
// - Handles idle timeouts & connection timeouts for us.
11+
712
console.log(
813
'🔐 DB SSL rejectUnauthorized:',
914
process.env.DB_SSL_REJECT_UNAUTHORIZED
1015
);
16+
1117
export const pool = new Pool({
1218
connectionString: process.env.DATABASE_URL,
1319
max: parseInt(process.env.DB_POOL_MAX || '8', 10),
@@ -25,10 +31,19 @@ pool.on('error', (err) =>
2531
console.error('[DB] Unexpected error on idle client', err)
2632
);
2733

34+
// Tiny helper to run a parameterized query using the shared pool.
35+
//
36+
// Example:
37+
// const { rows } = await query('select * from users where id = $1', [userId]);
38+
//
39+
// It also logs query duration in non‑production environments to help
40+
// track slow queries during development.
41+
2842
export async function query(sql, params = []) {
2943
const start = Date.now();
3044
const res = await pool.query(sql, params);
3145
const ms = Date.now() - start;
46+
3247
if (process.env.NODE_ENV !== 'production') {
3348
console.log(`SQL ${ms}ms: `, sql, params);
3449
}

server/lib/github-oauth.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
// this file is great, and organized.
2-
1+
// Build the GitHub OAuth authorization URL
32
export function buildAuthorizeUrl({ clientId, redirectUri, scopes, state }) {
43
const params = new URLSearchParams({
54
client_id: clientId,
@@ -11,6 +10,7 @@ export function buildAuthorizeUrl({ clientId, redirectUri, scopes, state }) {
1110
return `https://github.com/login/oauth/authorize?${params}`;
1211
}
1312

13+
// Exchange OAuth authorization code for an access token
1414
export async function exchangeCodeForToken({
1515
clientId,
1616
clientSecret,
@@ -40,6 +40,7 @@ export async function exchangeCodeForToken({
4040
return json;
4141
}
4242

43+
// Fetch the authenticated GitHub user profile
4344
export async function fetchGithubUser(accessToken) {
4445
const res = await fetch('https://api.github.com/user', {
4546
headers: {
@@ -56,6 +57,7 @@ export async function fetchGithubUser(accessToken) {
5657
return json;
5758
}
5859

60+
// Fetch the user's primary email address from GitHub
5961
export async function fetchPrimaryEmail(accessToken) {
6062
const res = await fetch('https://api.github.com/user/emails', {
6163
headers: {

server/lib/github-token.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { query } from '../db.js';
22

3+
// Get the GitHub access token for the user
34
export async function getGithubAccessTokenForUser(userId) {
45
if (process.env.GITHUB_PAT_OVERRIDE) {
56
return process.env.GITHUB_PAT_OVERRIDE;

server/lib/pipelineVersions.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
// Helper for storing versioned copies of workflow YAML files
12
import crypto from 'crypto';
23
import { query } from '../db.js';
34

5+
// Save a new pipeline YAML version with a SHA-256 hash for change tracking
46
export async function savePipelineVersion({
57
userId,
68
repoFullName,

server/lib/requireSession.js

Lines changed: 6 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,15 @@
1-
// import jwt from 'jsonwebtoken';
2-
3-
// const SESSION_SECRET = process.env.SESSION_SECRET || 'dev-secret';
4-
5-
// export function requireSession(req, res, next) {
6-
// let token = req.cookies?.mcp_session;
7-
// if (!token && req.headers.authorization && req.headers.authorization.startsWith('Bearer ')) {
8-
// token = req.headers.authorization.slice(7);
9-
// }
10-
// if (!token) return res.status(401).json({ error: 'No session token' });
11-
12-
// try {
13-
// const decoded = jwt.verify(token, SESSION_SECRET);
14-
// req.user = decoded;
15-
// next();
16-
// } catch (err) {
17-
// console.error('Session verify failed', err);
18-
// return res.status(401).json({ error: 'Invalid or expired session' });
19-
// }
20-
// }
21-
1+
// Middleware to validate the user's JWT session cookie
222
import jwt from 'jsonwebtoken';
233

4+
// Ensures a valid mcp_session JWT is present; attaches decoded user to req
245
export function requireSession(req, res, next) {
256
const raw = req.cookies?.mcp_session;
7+
268
if (!raw) return res.status(401).json({ error: 'No session' });
9+
2710
try {
28-
const user = jwt.verify(raw, process.env.JWT_SECRET); // MUST match the signer
29-
req.user = user; // { user_id, github_username, email, iat, exp }
11+
const user = jwt.verify(raw, process.env.JWT_SECRET);
12+
req.user = user;
3013
return next();
3114
} catch (e) {
3215
console.error('[requireSession] verify failed:', e.message);

server/lib/state.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
1+
// Simple in-memory OAuth state manager
12
import crypto from 'node:crypto';
23

34
const stateStore = new Map();
45
const STATE_TTL_MS = 10 * 60 * 1000; // 10 minutes
56

7+
// Generate a state token and store its metadata
68
export function createState(redirectTo = '/') {
79
const state = crypto.randomBytes(16).toString('hex');
810
stateStore.set(state, { createdAt: Date.now(), redirectTo });
911
return state;
1012
}
1113

14+
// Validate and consume a previously issued state token
1215
export function consumeState(state) {
1316
const item = stateStore.get(state);
1417
if (!item) return null;

server/routes/agent.js

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,12 @@ router.post('/wizard', async (req, res) => {
1515
try {
1616
const { repoUrl, provider, branch } = req.body;
1717
if (!repoUrl || !provider || !branch) {
18-
return res.status(400).json({ success: false, error: 'Missing required fields: repoUrl, provider, branch' });
18+
return res
19+
.status(400)
20+
.json({
21+
success: false,
22+
error: 'Missing required fields: repoUrl, provider, branch',
23+
});
1924
}
2025
const result = await runWizardAgent({ repoUrl, provider, branch });
2126
res.json({ success: true, data: result });
@@ -30,12 +35,14 @@ router.post('/pipeline', async (req, res) => {
3035
try {
3136
const { repoUrl } = req.body;
3237
if (!repoUrl) {
33-
return res.status(400).json({ success: false, error: 'Missing required field: repoUrl' });
38+
return res
39+
.status(400)
40+
.json({ success: false, error: 'Missing required field: repoUrl' });
3441
}
3542
const yaml = await pipeline_generator.handler({
3643
repo: repoUrl,
3744
provider: 'aws',
38-
template: 'node_app'
45+
template: 'node_app',
3946
});
4047
res.json({ success: true, data: yaml });
4148
} catch (err) {
@@ -48,7 +55,9 @@ router.post('/analyze', async (req, res) => {
4855
try {
4956
const { repoUrl } = req.body;
5057
if (!repoUrl) {
51-
return res.status(400).json({ success: false, error: 'Missing required field: repoUrl' });
58+
return res
59+
.status(400)
60+
.json({ success: false, error: 'Missing required field: repoUrl' });
5261
}
5362
const summary = await repo_reader.handler({ repo: repoUrl });
5463
res.json({ success: true, data: summary });
@@ -62,7 +71,9 @@ router.post('/deploy', async (req, res) => {
6271
try {
6372
const { provider } = req.body;
6473
if (!provider) {
65-
return res.status(400).json({ success: false, error: 'Missing required field: provider' });
74+
return res
75+
.status(400)
76+
.json({ success: false, error: 'Missing required field: provider' });
6677
}
6778
const deployLog = await oidc_adapter.handler({ provider });
6879
res.json({ success: true, data: deployLog });
@@ -76,4 +87,4 @@ router.get('/status', (_req, res) => {
7687
res.json({ success: true, data: { ok: true, uptime: process.uptime() } });
7788
});
7889

79-
export default router;
90+
export default router;

0 commit comments

Comments
 (0)