Skip to content
This repository was archived by the owner on May 12, 2026. It is now read-only.

Merge pull request #4 from hngprojects/feature/auth-helpers#12

Open
Rayflix55 wants to merge 5 commits into
devfrom
rayflix-update
Open

Merge pull request #4 from hngprojects/feature/auth-helpers#12
Rayflix55 wants to merge 5 commits into
devfrom
rayflix-update

Conversation

@Rayflix55
Copy link
Copy Markdown
Contributor

@Rayflix55 Rayflix55 commented May 10, 2026

forgot password and login page

Summary by CodeRabbit

  • New Features

    • Added "Forgot Password" page with email validation and password reset functionality
    • Enhanced login experience with OAuth sign-in options, magic link authentication, password visibility toggle, and remember-me functionality
  • Chores

    • Added automated build and deployment pipelines for staging and production environments
    • Integrated security scanning capability for supply-chain threat detection

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 10, 2026

📝 Walkthrough

Walkthrough

This PR adds three independent subsystems: a complete CI/CD deployment pipeline (build artifact creation and automated staging/production deployments), a redesigned authentication experience with a new forgot-password page and enhanced login UI, and a comprehensive cross-platform security scanner tool for detecting supply-chain IOCs and persistence mechanisms.

Changes

CI/CD Deployment Pipeline

Layer / File(s) Summary
Build Artifact Creation
.github/workflows/build-and-upload.yml
Workflow triggers on push to staging/main, checks out code, sets up Node.js 20 and pnpm v9, builds the Next.js app, archives .next, public, package.json, and pnpm-lock.yaml into a tarball, and uploads it as a 1-day retention artifact.
Staging Deployment
.github/workflows/staging-deployment.yml
Workflow triggered on successful build-and-upload completion on staging branch. Downloads artifact, decodes environment secret, transfers via SCP to staging server, SSH extracts archive, installs production dependencies, and restarts frontend-staging PM2 process.
Production Deployment
.github/workflows/production-deployment.yml
Workflow triggered manually or on successful build-and-upload completion on main branch. Downloads artifact, decodes environment secret, transfers via SCP to production server, SSH extracts archive, installs production dependencies, and restarts frontend-production PM2 process.

Authentication UI Enhancement

Layer / File(s) Summary
Forgot Password Component
src/app/forgot-password/ForgotPassword.tsx
New client-side component that manages email validation, POST request to /api/v1/auth/forgot-password, and displays either an email-entry form or success confirmation with framer-motion animated transitions.
Forgot Password Route
src/app/forgot-password/page.tsx
Next.js page entry that imports and re-exports the ForgotPassword component as the default route handler.
Enhanced Login Page
src/app/login/page.tsx
Redesigned login with react-hook-form and zod validation, password visibility toggle, remember-me checkbox, OAuth buttons (Google/GitHub), magic-link button, two-panel layout with marketing sidebar, animated entry, conditional success/error toasts, and links to forgot-password and signup.

Supply-Chain Security Scanner

Layer / File(s) Summary
Core Scanning Architecture
lazarus_scanner.py (Lines 1–195)
Module initialization with platform detection, IOC regex patterns for supply-chain indicators, target filename lists, skip directories. Implements target discovery, file scanning with regex-based IOC detection, file quarantine (copy to timestamped backup), and config cleaning (truncate after legitimate export/module markers).
Process Detection & Termination
lazarus_scanner.py (Lines 201–380)
Cross-platform process enumeration (Linux /proc, Windows PowerShell CIM, POSIX ps), heuristic scoring of processes based on IOC indicators (C2 endpoints, obfuscation, TronWeb/Solana, eval/atob, temp paths), conversion to ProcessFinding records, and OS-specific process termination with user confirmation.
Persistence Mechanism Detection
lazarus_scanner.py (Lines 386–479)
Checks shell RC files, cron entries, macOS LaunchAgents plists, and Windows scheduled tasks/registry for suspicious references (node, curl, wget, powershell-encoded commands); aggregates findings via all_persistence_checks().
Hardening Actions & CLI
lazarus_scanner.py (Lines 485–747)
Hardening routines (npm ignore-scripts, git credential-cache clearing, hosts file domain blocking checks). CLI argument parsing (--scan-home, --fix, --kill, --harden, --all, --watch, --json, --no-process-check). Orchestrates scan/fix/kill/harden execution, renders text or JSON reports, implements watch-mode looping, exits non-zero on findings.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • Muizzyranking
🚥 Pre-merge checks | ✅ 2 | ❌ 3

❌ Failed checks (3 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning The title refers to a merge commit format rather than describing the actual changes; it should summarize what the PR does rather than stating it is a merge pull request. Change the title to something like 'Add forgot password page and enhance login UI' to clearly describe the main changes being introduced.
Description check ⚠️ Warning The PR description is incomplete; it lacks required sections like 'Closes #issue_number', 'What were you told to do', detailed changes, and checklist items per the template. Fill in the description template with the linked issue number, a clear explanation of requirements, detailed file changes, and check all applicable boxes in the checklist.
Docstring Coverage ⚠️ Warning Docstring coverage is 7.14% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch rayflix-update

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Warning

⚠️ This pull request might be slop. It has been flagged by CodeRabbit slop detection and should be reviewed carefully.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a comprehensive security scanner script (lazarus_scanner.py) designed to detect and mitigate Lazarus supply-chain attacks across multiple platforms. Additionally, it implements a new 'Forgot Password' page and refactors the 'Login' page to include Zod validation, OAuth integration, and enhanced UI components. The review feedback highlights several areas for improvement: ensuring consistent validation schemas, avoiding brittle navigation patterns using setTimeout, moving the Toaster component to a root layout, adhering to standard Tailwind CSS classes, and preventing layout shifts in form inputs. There is also a suggestion to improve the uniqueness of backup file naming in the scanner script.

Comment thread src/app/login/page.tsx Outdated
Comment on lines +35 to +41
const LoginSchema = z.object({
email: z.string().email({ message: 'Invalid email address' }),
password: z
.string()
.min(6, { message: 'Password must be at least 6 characters' }),
rememberMe: z.boolean(),
})
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The LoginSchema is redefined locally with a minimum password length of 6, which is inconsistent with the shared LoginSchema in src/schemas/auth.ts (which requires min(1)) and the RegisterSchema (which requires min(8)). It is recommended to import and extend the shared schema to ensure consistent validation rules across the application.

Comment thread src/app/login/page.tsx Outdated
toast.success('Login success', {
description: 'Redirecting to workspace',
})
setTimeout(() => router.push('/'), 1000)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using setTimeout to delay navigation after a success toast is a brittle pattern. If the component unmounts or the navigation takes longer than expected, it can lead to a poor user experience. Consider using the toast's callback functions or navigating immediately, as modern toast libraries like sonner handle persistence across routes.

Comment thread src/app/login/page.tsx Outdated

{/* Right panel */}
<div className="flex w-full items-center justify-center p-6 sm:p-12 lg:w-[60%]">
<Toaster position="top-center" />
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The Toaster component should generally be placed in the root layout (e.g., src/app/layout.tsx) rather than in individual page components. This ensures that toast notifications persist during page transitions and prevents multiple toaster instances from being mounted.

Comment thread src/app/login/page.tsx Outdated
<motion.div
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
className="flex w-full max-w-130 flex-col rounded-[40px] bg-white p-8 shadow-[0_8px_30px_rgb(0,0,0,0.04)] sm:p-12 md:p-16"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

max-w-130 is not a standard Tailwind CSS utility class. If this is not a custom class defined in your configuration, consider using a standard size like max-w-lg or an arbitrary value like max-w-[520px].

References
  1. Adherence to standard Tailwind CSS utility classes for maintainability. (link)

<motion.div
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
className="flex w-full max-w-130 flex-col rounded-[40px] bg-white p-8 shadow-[0_8px_30px_rgb(0,0,0,0.04)] sm:p-12 md:p-16"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

max-w-130 is not a standard Tailwind CSS utility class. If this is not a custom class defined in your configuration, consider using a standard size like max-w-lg or an arbitrary value like max-w-[520px].

References
  1. Adherence to standard Tailwind CSS utility classes for maintainability. (link)

Comment on lines +170 to +174
className={`w-full border bg-[#FFFFFF] ${
status === 'error'
? 'border-[#FF5630]'
: 'border-[#919EAB]/30'
} rounded-xl ${status === 'error' ? 'pl-4' : 'pl-12'} py-3.5 pr-12 text-[#1A1A1A] placeholder-[#919EAB] transition-all focus:ring-1 focus:ring-[#007B55] focus:outline-none`}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The conditional padding (pl-4 vs pl-12) causes a layout shift when the input enters an error state because the Mail icon is hidden. It is better to keep the padding consistent and keep the icon visible (perhaps with a different color) to avoid the text jumping.

Comment thread lazarus_scanner.py


def quarantine(path: Path) -> Path:
backup = path.with_suffix(path.suffix + f".malware-backup-{int(time.time())}")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using int(time.time()) as a suffix for the backup file name can lead to collisions if the script processes the same file multiple times within the same second. Consider using a more precise timestamp or appending a short random string to ensure the backup path is unique.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 20

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/build-and-upload.yml:
- Around line 25-26: Update the CI install step so pnpm fails if the lockfile is
out of sync: in the workflow step named "Install dependencies" replace the
current run command that executes "pnpm install" with a run that uses the
frozen-lockfile flag (i.e., use "pnpm install --frozen-lockfile") so CI will
error if pnpm-lock.yaml would be modified.
- Around line 31-32: The tar command in the "Archive production artifacts" step
(the job named "Archive production artifacts" that runs tar -czf
agentforge-build.tar.gz .next public package.json pnpm-lock.yaml) omits
next.config.ts, which Next.js needs at runtime; update that tar invocation to
include next.config.ts so the production archive contains the runtime Next
config (add next.config.ts to the list of files passed to tar).

In @.github/workflows/production-deployment.yml:
- Around line 17-19: The deployment is currently un-gated; configure the GitHub
Environment named "production" (the environment referenced by environment: name:
"production" in the workflow) to require manual approval by adding one or more
Required reviewers under Settings → Environments → production → Required
reviewers so the deploy job will pause and require an approver to click
"Approve" before the job runs; after adding reviewers, verify the workflow still
references environment: "production" (and its url if needed) so GitHub enforces
the approval gate.

In @.github/workflows/staging-deployment.yml:
- Around line 17-19: The environment.url values currently use hostnames without
a TLD (e.g., "https://staging.agent-forge") which will not resolve; update the
environment.url entries for both environment.name "staging" (in this file) and
the equivalent entry in production-deployment.yml (environment.name
"production") to the full deployed hostnames including your domain (e.g.,
"https://staging.agent-forge.<your-domain>" and
"https://agent-forge.<your-domain>") so the GitHub Environments "View
deployment" links resolve correctly.
- Around line 34-57: Replace password-based auth in the
appleboy/scp-action@v0.1.7 and appleboy/ssh-action@v1.2.1 steps: stop using the
password: field and instead use a private key from secrets (e.g.
secrets.SSH_PRIVATE_KEY) and the corresponding fields the actions support
(private_key / key or key_path) while keeping host and username as-is; add host
fingerprint pinning using the fingerprint: field to the ssh/scp actions to
mitigate MITM; ensure the server’s authorized_keys contains the matching public
key; make the same change in production-deployment.yml so both workflows use SSH
key authentication and host pinning instead of password authentication.
- Around line 3-15: The deploy job's if condition currently requires
github.event.workflow_run.conclusion == 'success', which is null for manual
workflow_dispatch runs; update the deploy job conditional (job name: deploy) to
gate on the event source so it runs either when the triggering workflow_run
completed successfully OR when the event is workflow_dispatch (e.g. use a
condition that checks github.event_name == 'workflow_run' &&
github.event.workflow_run.conclusion == 'success' || github.event_name ==
'workflow_dispatch'), and also guard the "Download build artifact" step's run-id
input so it is omitted or sourced from a workflow_dispatch input when
github.event_name == 'workflow_dispatch' to avoid using
github.event.workflow_run.id which is absent for manual runs.
- Around line 52-57: Change the pm2 invocation to be resilient to first-time
deploys by replacing the brittle "pm2 restart frontend-staging --update-env"
usage with a reload-or-start pattern (e.g., "pm2 reload frontend-staging
--update-env || pm2 start ...") so the process is created if it doesn't exist;
also ensure the non-interactive SSH shell can find pnpm by sourcing the user's
shell rc (or otherwise adding pnpm to PATH) before running "pnpm install --prod"
so pnpm is available in the CI job. Target the script block containing the "pnpm
install --prod" and "pm2 restart frontend-staging --update-env" lines when
making these edits.

In `@lazarus_scanner.py`:
- Around line 174-194: The truncation in clean_file is unsafe: the regexes for
patterns 3/4 use non-greedy brace matching and the code picks the earliest match
across end_markers (cut = m.end() ... min), which can truncate nested objects or
hit false-positives; change clean_file to (1) for patterns that match object
literals (currently the \{[\s\S]*?\} entries) stop using non-greedy regex and
instead implement a small brace-balanced scanner that walks the text from each
candidate "{", tracking braces while respecting strings/comments so you get the
correct closing brace, and (2) compute cut using the last-valid occurrence per
pattern (use finditer and take max(m.end()) per pattern) and only accept a cut
that is after the IOC offset returned by scan_file/quarantine; only then apply
the trim and keep the existing safety checks (50B threshold, writing via
path.write_text).

In `@src/app/forgot-password/ForgotPassword.tsx`:
- Around line 224-228: The "Contact support" anchor in ForgotPassword.tsx (and
the similar instance around lines 300-304) uses href="#" which is inaccessible
and causes page jumps; update the anchor to either a real route (e.g.,
href="/support") or a mailto link (e.g., href="mailto:support@example.com") if
it should open the user's email client, or convert it to a button element with
an onClick handler that opens the support modal/route and include role="button"
and tabIndex={0} for accessibility; ensure the changed element in the
ForgotPassword component maintains visible focus styles and keyboard activation
(Enter/Space) for screen reader and keyboard users.
- Around line 64-74: The catch block in ForgotPassword.tsx currently swallows
all errors and runs mock/demo logic; update the catch to accept an error
parameter (e.g., catch (err)) and remove the setTimeout mock flow, instead
handle real errors by inspecting err (network/response) and setStatus('error')
and setErrorMessage with a meaningful message (or err.message) and optionally
log the error; ensure the promise rejection path for the function (e.g., the
submit/handleForgotPassword function) surfaces failures appropriately rather
than returning mocked success.
- Around line 208-211: Remove the GitHub OAuth button JSX from the
ForgotPassword component: delete the <button ...> block that contains <Github
className="h-5 w-5" /> and "Sign in with GitHub" so the forgot-password flow
only shows recovery actions; also remove any now-unused references/imports such
as the Github icon import and any OAuth handler (e.g., signInWithGitHub or
onGithubSignIn) from the same file to avoid unused symbols and adjust
surrounding layout/styling if needed to preserve spacing.
- Around line 282-287: The Resend Email button allows spamming; add a
client-side cooldown by introducing a cooldown state (e.g., cooldownSeconds via
useState) inside the ForgotPassword component and disable the button while
cooldownSeconds > 0; update the onClick handler referenced (currently calling
setStatus('idle')) to also start the cooldown (setCooldownSeconds to a
configured value), and use a useEffect with a timer to decrement cooldownSeconds
every second and clear the interval on unmount; also change the button rendering
to show disabled styling and the remaining seconds (e.g., "Resend Email (xxs)")
when cooldown is active so users get feedback.
- Around line 282-287: The "Resend Email" button in ForgotPassword currently
only calls setStatus('idle') which clears UI but doesn't trigger the API; update
it to call the same submission logic used by the form (e.g., invoke the submit
handler or extracted resendEmail/sendResetEmail function) so clicking "Resend
Email" actually sends the request again; if submission logic is inline in
handleSubmit, extract that logic into a reusable function (resendResetEmail or
sendResetEmail) and call it from both handleSubmit and the Resend Email button,
preserving status updates and loading/disabled states.

In `@src/app/login/page.tsx`:
- Around line 80-95: The login flow currently implemented in onSubmit (and
related OAuth handlers) lacks CSRF protection; update the client and server flow
to generate and validate CSRF tokens: implement a Server Action or middleware
that issues a cryptographically strong CSRF token (or use NextAuth/Auth.js to
handle this automatically), store the token in a secure httpOnly cookie (or use
double-submit cookie) and include the token in the login form submission (refer
to onSubmit and LoginFormValues for the form payload), then validate that token
server‑side before processing authentication and before completing any OAuth
callback handlers; ensure tokens are rotated/expired and validation logic is
invoked in the server endpoint that handles the login/OAuth exchange.
- Around line 80-95: The onSubmit function currently contains mock logic
(setTimeout and special-case 'error@example.com'); replace it with a real async
authentication call: call your auth API/client (e.g., await auth.login(values)
or fetch POST to your login endpoint) inside the startTransition block, handle
success by showing toast.success and calling router.push('/'), and handle
failures by catching the error and calling toast.error with the error message;
remove the hardcoded setTimeout and conditional email check and ensure you
return/await the real promise and properly type errors for LoginFormValues,
startTransition, toast, and router.push usage.
- Around line 37-39: The password Zod schema currently enforces .min(6) which is
too weak; update the validator for the password field (the password
z.string().min(...) call) to .min(8) (or higher), and adjust the associated
error message from "Password must be at least 6 characters" to "Password must be
at least 8 characters" (and update any related client/server validation or tests
that expect the old minimum).
- Line 96: The variable rememberMe returned from useWatch is unused; remove the
redundant call "const rememberMe = useWatch({ control: form.control, name:
'rememberMe' })" from the component so state is only read/updated via the
existing checkbox handlers that call form.setValue; ensure no other code
references rememberMe before deleting.
- Around line 331-337: Links for "Terms of Service" and "Privacy Policy" are
using href="#" which causes jumps and is inaccessible; replace the dummy anchors
with proper routes (e.g., href="/terms" and href="/privacy") and use Next.js
<Link> if available (or regular <a> with real hrefs) for the "Terms of Service"
and "Privacy Policy" elements in the login page (the anchors containing the text
"Terms of Service" and "Privacy Policy"); ensure the className and hover styles
are preserved and remove the '#' handlers so clicks navigate to the correct
pages.
- Line 92: Replace the untracked setTimeout call that calls router.push('/')
with either an immediate navigation or a cancellable timer: remove the bare
setTimeout(() => router.push('/'), 1000) and call router.push('/') directly, or
if you need the delay, store the timer id returned by setTimeout in a ref/state
and clear it in a useEffect cleanup (clearTimeout(timerId)) so the callback
won't run after the component unmounts; refer to the existing setTimeout and
router.push('/') usage in the page component to locate where to apply this
change.
- Around line 164-186: The OAuth and magic-link buttons in page.tsx (the Button
elements rendering GoogleLogo and Github and their onClick handlers) are
placeholders that only fire toast messages; implement real flows or remove them.
Either wire these buttons to real auth functions in src/lib/api/auth.ts (e.g.,
add startGoogleOAuth(), startGithubOAuth(), sendMagicLinkEmail()) and update
.env.sample with CLIENT_ID/CLIENT_SECRET and redirect URIs, or remove the Button
nodes and related UI (GoogleLogo, Github usage) to avoid exposing dead actions;
ensure isLoading handling and disabled state remain correct and update any
imports/usages accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: fc6ea27d-662d-48f2-9c33-ac33938c41b4

📥 Commits

Reviewing files that changed from the base of the PR and between f648a6d and 2f93c3d.

📒 Files selected for processing (7)
  • .github/workflows/build-and-upload.yml
  • .github/workflows/production-deployment.yml
  • .github/workflows/staging-deployment.yml
  • lazarus_scanner.py
  • src/app/forgot-password/ForgotPassword.tsx
  • src/app/forgot-password/page.tsx
  • src/app/login/page.tsx

Comment on lines +25 to +26
- name: Install dependencies
run: pnpm install
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Use --frozen-lockfile for reproducible CI installs.

Without --frozen-lockfile, pnpm will silently rewrite pnpm-lock.yaml if it drifts from package.json, producing non-reproducible build artifacts and masking lockfile mistakes in PRs. CI installs should fail loudly instead.

♻️ Proposed fix
     - name: Install dependencies
-      run: pnpm install
+      run: pnpm install --frozen-lockfile
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/build-and-upload.yml around lines 25 - 26, Update the CI
install step so pnpm fails if the lockfile is out of sync: in the workflow step
named "Install dependencies" replace the current run command that executes "pnpm
install" with a run that uses the frozen-lockfile flag (i.e., use "pnpm install
--frozen-lockfile") so CI will error if pnpm-lock.yaml would be modified.

Comment on lines +31 to +32
- name: Archive production artifacts
run: tar -czf agentforge-build.tar.gz .next public package.json pnpm-lock.yaml
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify which root-level files Next.js will need at runtime.
fd -HI -d 1 '^(next\.config\.(js|ts|mjs|cjs)|\.npmrc|\.env\.production|instrumentation\.(t|j)s)$'

Repository: hngprojects/agentforge-fe

Length of output: 84


Add next.config.ts to the tar archive.

The repository contains next.config.ts at the root, but it is not included in the current tar archive. Since next start requires this file to read runtime configuration (rewrites, redirects, output mode, basePath, images config), the production server will fail to apply these settings and behave differently from local builds.

♻️ Proposed fix
     - name: Archive production artifacts
-      run: tar -czf agentforge-build.tar.gz .next public package.json pnpm-lock.yaml
+      run: tar -czf agentforge-build.tar.gz .next public package.json pnpm-lock.yaml next.config.ts
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- name: Archive production artifacts
run: tar -czf agentforge-build.tar.gz .next public package.json pnpm-lock.yaml
- name: Archive production artifacts
run: tar -czf agentforge-build.tar.gz .next public package.json pnpm-lock.yaml next.config.ts
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/build-and-upload.yml around lines 31 - 32, The tar command
in the "Archive production artifacts" step (the job named "Archive production
artifacts" that runs tar -czf agentforge-build.tar.gz .next public package.json
pnpm-lock.yaml) omits next.config.ts, which Next.js needs at runtime; update
that tar invocation to include next.config.ts so the production archive contains
the runtime Next config (add next.config.ts to the list of files passed to tar).

Comment on lines +17 to +19
environment:
name: "production"
url: "https://agent-forge"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Gate production deploys with a manual approval.

Right now, every successful build on main deploys straight to production with no human in the loop. Configure the production GitHub Environment with required reviewers (Settings → Environments → production → Required reviewers) so an approver must click "Approve" before the deploy job runs. This is the single highest-leverage safety control for a prod pipeline triggered automatically off main.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/production-deployment.yml around lines 17 - 19, The
deployment is currently un-gated; configure the GitHub Environment named
"production" (the environment referenced by environment: name: "production" in
the workflow) to require manual approval by adding one or more Required
reviewers under Settings → Environments → production → Required reviewers so the
deploy job will pause and require an approver to click "Approve" before the job
runs; after adding reviewers, verify the workflow still references environment:
"production" (and its url if needed) so GitHub enforces the approval gate.

Comment on lines +3 to +15
on:
workflow_dispatch:
workflow_run:
workflows: ["Build and Upload"]
types:
- completed
branches:
- staging

jobs:
deploy:
runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion == 'success' }}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Manual workflow_dispatch runs are silently skipped.

On workflow_dispatch, github.event.workflow_run is null, so github.event.workflow_run.conclusion == 'success' evaluates to false and the deploy job never runs. The "manual deploy" trigger is effectively dead code. Gate the condition on the event source.

🐛 Proposed fix
   deploy:
     runs-on: ubuntu-latest
-    if: ${{ github.event.workflow_run.conclusion == 'success' }}
+    if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }}

Note: when triggered by workflow_dispatch, github.event.workflow_run.id is also unavailable, so the "Download build artifact" step's run-id input must be guarded too (e.g. omit run-id for manual runs to fall back to the latest successful run, or accept a workflow_dispatch input for the run id).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/staging-deployment.yml around lines 3 - 15, The deploy
job's if condition currently requires github.event.workflow_run.conclusion ==
'success', which is null for manual workflow_dispatch runs; update the deploy
job conditional (job name: deploy) to gate on the event source so it runs either
when the triggering workflow_run completed successfully OR when the event is
workflow_dispatch (e.g. use a condition that checks github.event_name ==
'workflow_run' && github.event.workflow_run.conclusion == 'success' ||
github.event_name == 'workflow_dispatch'), and also guard the "Download build
artifact" step's run-id input so it is omitted or sourced from a
workflow_dispatch input when github.event_name == 'workflow_dispatch' to avoid
using github.event.workflow_run.id which is absent for manual runs.

Comment on lines +17 to +19
environment:
name: "staging"
url: "https://staging.agent-forge"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Environment URL is missing a TLD.

https://staging.agent-forge isn't a resolvable hostname — the "View deployment" link in the GitHub Environments UI will 404 / DNS-fail. Same in production-deployment.yml (https://agent-forge). Use the actual deployed hostnames (e.g. https://staging.agent-forge.<your-domain>).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/staging-deployment.yml around lines 17 - 19, The
environment.url values currently use hostnames without a TLD (e.g.,
"https://staging.agent-forge") which will not resolve; update the
environment.url entries for both environment.name "staging" (in this file) and
the equivalent entry in production-deployment.yml (environment.name
"production") to the full deployed hostnames including your domain (e.g.,
"https://staging.agent-forge.<your-domain>" and
"https://agent-forge.<your-domain>") so the GitHub Environments "View
deployment" links resolve correctly.

Comment thread src/app/login/page.tsx Outdated
Comment on lines +80 to +95
const onSubmit = (values: LoginFormValues) => {
startTransition(async () => {
await new Promise((resolve) => setTimeout(resolve, 1500))

if (values.email === 'error@example.com') {
toast.error('Login failed', {
description: 'An error occurred during login',
})
} else {
toast.success('Login success', {
description: 'Redirecting to workspace',
})
setTimeout(() => router.push('/'), 1000)
}
})
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial | 🏗️ Heavy lift

No CSRF protection for authentication actions.

The login form and OAuth flows do not include CSRF tokens or other anti-CSRF measures. Modern authentication systems should protect against cross-site request forgery attacks.

Consider implementing CSRF protection for authentication endpoints:

  1. Use Next.js middleware to generate and validate CSRF tokens
  2. Include CSRF tokens in all authentication forms
  3. Validate tokens server-side before processing login/OAuth requests
  4. Consider using established auth libraries like NextAuth.js/Auth.js that handle CSRF automatically

For Next.js App Router, you can:

  • Generate CSRF tokens in Server Actions
  • Use secure, httpOnly cookies for session management
  • Implement the Synchronizer Token Pattern or Double Submit Cookie pattern

Also applies to: 164-186

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/login/page.tsx` around lines 80 - 95, The login flow currently
implemented in onSubmit (and related OAuth handlers) lacks CSRF protection;
update the client and server flow to generate and validate CSRF tokens:
implement a Server Action or middleware that issues a cryptographically strong
CSRF token (or use NextAuth/Auth.js to handle this automatically), store the
token in a secure httpOnly cookie (or use double-submit cookie) and include the
token in the login form submission (refer to onSubmit and LoginFormValues for
the form payload), then validate that token server‑side before processing
authentication and before completing any OAuth callback handlers; ensure tokens
are rotated/expired and validation logic is invoked in the server endpoint that
handles the login/OAuth exchange.

⚠️ Potential issue | 🔴 Critical | 🏗️ Heavy lift

Critical: Remove mock login logic from production code.

The onSubmit function contains hardcoded mock logic with setTimeout and special handling for error@example.com. This will break in production when integrated with a real authentication API.

🔧 Proposed fix: Implement proper API integration
  const onSubmit = (values: LoginFormValues) => {
    startTransition(async () => {
-     await new Promise((resolve) => setTimeout(resolve, 1500))
-
-     if (values.email === 'error@example.com') {
-       toast.error('Login failed', {
-         description: 'An error occurred during login',
-       })
-     } else {
-       toast.success('Login success', {
-         description: 'Redirecting to workspace',
-       })
-       setTimeout(() => router.push('/'), 1000)
-     }
+     try {
+       const response = await fetch('/api/v1/auth/login', {
+         method: 'POST',
+         headers: { 'Content-Type': 'application/json' },
+         body: JSON.stringify({
+           email: values.email,
+           password: values.password,
+           rememberMe: values.rememberMe,
+         }),
+       })
+
+       if (!response.ok) {
+         const error = await response.json()
+         toast.error('Login failed', {
+           description: error.message || 'Invalid credentials',
+         })
+         return
+       }
+
+       const data = await response.json()
+       // Store auth token/session as needed
+       
+       toast.success('Login success', {
+         description: 'Redirecting to workspace',
+       })
+       router.push('/')
+     } catch (error) {
+       console.error('Login error:', error)
+       toast.error('Login failed', {
+         description: 'Unable to connect to server',
+       })
+     }
    })
  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const onSubmit = (values: LoginFormValues) => {
startTransition(async () => {
await new Promise((resolve) => setTimeout(resolve, 1500))
if (values.email === 'error@example.com') {
toast.error('Login failed', {
description: 'An error occurred during login',
})
} else {
toast.success('Login success', {
description: 'Redirecting to workspace',
})
setTimeout(() => router.push('/'), 1000)
}
})
}
const onSubmit = (values: LoginFormValues) => {
startTransition(async () => {
try {
const response = await fetch('/api/v1/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
email: values.email,
password: values.password,
rememberMe: values.rememberMe,
}),
})
if (!response.ok) {
const error = await response.json()
toast.error('Login failed', {
description: error.message || 'Invalid credentials',
})
return
}
const data = await response.json()
// Store auth token/session as needed
toast.success('Login success', {
description: 'Redirecting to workspace',
})
router.push('/')
} catch (error) {
console.error('Login error:', error)
toast.error('Login failed', {
description: 'Unable to connect to server',
})
}
})
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/login/page.tsx` around lines 80 - 95, The onSubmit function currently
contains mock logic (setTimeout and special-case 'error@example.com'); replace
it with a real async authentication call: call your auth API/client (e.g., await
auth.login(values) or fetch POST to your login endpoint) inside the
startTransition block, handle success by showing toast.success and calling
router.push('/'), and handle failures by catching the error and calling
toast.error with the error message; remove the hardcoded setTimeout and
conditional email check and ensure you return/await the real promise and
properly type errors for LoginFormValues, startTransition, toast, and
router.push usage.

Comment thread src/app/login/page.tsx Outdated
toast.success('Login success', {
description: 'Redirecting to workspace',
})
setTimeout(() => router.push('/'), 1000)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Potential memory leak: router.push inside setTimeout without cleanup.

The component may unmount before the setTimeout callback fires, causing router.push('/') to execute on an unmounted component. While Next.js router is generally safe, this pattern can cause issues and should use proper cleanup or immediate navigation.

🔧 Proposed fix: Navigate immediately or add cleanup

Option 1 - Navigate immediately (preferred):

       toast.success('Login success', {
         description: 'Redirecting to workspace',
       })
-      setTimeout(() => router.push('/'), 1000)
+      router.push('/')

Option 2 - Add proper cleanup if delay is needed:

+ useEffect(() => {
+   return () => {
+     // Component cleanup
+   }
+ }, [])

  const onSubmit = (values: LoginFormValues) => {
    startTransition(async () => {
+     let timeoutId: NodeJS.Timeout
      // ... success case
      toast.success('Login success', {
        description: 'Redirecting to workspace',
      })
-     setTimeout(() => router.push('/'), 1000)
+     timeoutId = setTimeout(() => router.push('/'), 1000)
+     // Note: still needs proper cleanup strategy
    })
  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
setTimeout(() => router.push('/'), 1000)
toast.success('Login success', {
description: 'Redirecting to workspace',
})
router.push('/')
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/login/page.tsx` at line 92, Replace the untracked setTimeout call
that calls router.push('/') with either an immediate navigation or a cancellable
timer: remove the bare setTimeout(() => router.push('/'), 1000) and call
router.push('/') directly, or if you need the delay, store the timer id returned
by setTimeout in a ref/state and clear it in a useEffect cleanup
(clearTimeout(timerId)) so the callback won't run after the component unmounts;
refer to the existing setTimeout and router.push('/') usage in the page
component to locate where to apply this change.

Comment thread src/app/login/page.tsx Outdated
}
})
}
const rememberMe = useWatch({ control: form.control, name: 'rememberMe' })
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial | 💤 Low value

Unused value: rememberMe from useWatch.

The rememberMe value is extracted using useWatch but never used in the component. The checkbox already reads and writes directly via form.setValue, making this line redundant.

🧹 Remove unused variable
- const rememberMe = useWatch({ control: form.control, name: 'rememberMe' })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const rememberMe = useWatch({ control: form.control, name: 'rememberMe' })
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/login/page.tsx` at line 96, The variable rememberMe returned from
useWatch is unused; remove the redundant call "const rememberMe = useWatch({
control: form.control, name: 'rememberMe' })" from the component so state is
only read/updated via the existing checkbox handlers that call form.setValue;
ensure no other code references rememberMe before deleting.

Comment thread src/app/login/page.tsx Outdated
Comment on lines +164 to +186
<Button
type="button"
onClick={() => toast.info('Google login initiated')}
disabled={isLoading}
variant="outline"
className="h-12 w-full gap-3 rounded-xl border-[#919EAB]/30 font-semibold text-[#454F5B]"
>
<GoogleLogo />
<span className="mx-1 h-4 w-px bg-[#919EAB]/30" />
<span>Continue with Google</span>
</Button>
<Button
type="button"
onClick={() => toast.info('GitHub login initiated')}
disabled={isLoading}
variant="outline"
className="h-12 w-full gap-3 rounded-xl border-[#919EAB]/30 font-semibold text-[#454F5B]"
>
<Github className="h-5 w-5" />
<span className="mx-1 h-4 w-px bg-[#919EAB]/30" />
<span>Continue with GitHub</span>
</Button>
</div>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if OAuth/magic link API routes exist
fd -t f "route.ts|route.js" src/app/api | xargs rg -l "auth.*(google|github|magic)" 

# Check for auth provider configuration
rg -i "google.*client.*id|github.*client.*id|oauth.*provider" --type ts --type js -g '!node_modules'

Repository: hngprojects/agentforge-fe

Length of output: 168


🏁 Script executed:

# First, explore the repository structure
git ls-files src/ | head -20

Repository: hngprojects/agentforge-fe

Length of output: 638


🏁 Script executed:

# Look for the login page file
fd -t f "page.tsx" src/ | grep -i login

Repository: hngprojects/agentforge-fe

Length of output: 92


🏁 Script executed:

# Check for any auth-related files or directories
fd -t f -i "auth" src/

Repository: hngprojects/agentforge-fe

Length of output: 155


🏁 Script executed:

# Read the login page to see the button implementations
cat -n src/app/login/page.tsx | sed -n '160,190p'

Repository: hngprojects/agentforge-fe

Length of output: 1494


🏁 Script executed:

# Also check the other lines mentioned
cat -n src/app/login/page.tsx | sed -n '303,320p'

Repository: hngprojects/agentforge-fe

Length of output: 769


🏁 Script executed:

# Check the auth API implementation
cat -n src/lib/api/auth.ts

Repository: hngprojects/agentforge-fe

Length of output: 5091


🏁 Script executed:

# Check for environment variables related to OAuth
find . -name ".env*" -type f | grep -v node_modules

Repository: hngprojects/agentforge-fe

Length of output: 83


🏁 Script executed:

# Search for OAuth-related packages in package.json
cat package.json | grep -i "oauth\|google\|github" | head -20

Repository: hngprojects/agentforge-fe

Length of output: 51


🏁 Script executed:

# Search for any OAuth implementation in the codebase
rg "oauth|googleLogin|githubLogin|magicLink" --type ts --type js --type tsx --type jsx -i

Repository: hngprojects/agentforge-fe

Length of output: 97


🏁 Script executed:

# Read the .env.sample to see what OAuth configuration is expected
cat .env.sample

Repository: hngprojects/agentforge-fe

Length of output: 97


🏁 Script executed:

# Retry OAuth search with correct file types
rg "oauth|googleLogin|githubLogin|magicLink" --type ts --type js -i

Repository: hngprojects/agentforge-fe

Length of output: 124


Connect OAuth and magic link flows, or remove these placeholder buttons.

The Google OAuth, GitHub OAuth, and magic link buttons display toast notifications only, with no actual authentication implementations. The src/lib/api/auth.ts file contains no corresponding OAuth or magic link functions, and the .env.sample lacks OAuth provider credentials. Either integrate these flows with real authentication providers and a magic link email system before release, or remove the buttons to prevent user confusion.

Also applies to: 307-314

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/login/page.tsx` around lines 164 - 186, The OAuth and magic-link
buttons in page.tsx (the Button elements rendering GoogleLogo and Github and
their onClick handlers) are placeholders that only fire toast messages;
implement real flows or remove them. Either wire these buttons to real auth
functions in src/lib/api/auth.ts (e.g., add startGoogleOAuth(),
startGithubOAuth(), sendMagicLinkEmail()) and update .env.sample with
CLIENT_ID/CLIENT_SECRET and redirect URIs, or remove the Button nodes and
related UI (GoogleLogo, Github usage) to avoid exposing dead actions; ensure
isLoading handling and disabled state remain correct and update any
imports/usages accordingly.

Comment thread src/app/login/page.tsx Outdated
Comment on lines +331 to +337
<a href="#" className="font-bold text-[#007B55] hover:underline">
Terms of Service
</a>{' '}
and{' '}
<a href="#" className="font-bold text-[#007B55] hover:underline">
Privacy Policy
</a>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fix inaccessible Terms of Service and Privacy Policy links.

The ToS and Privacy Policy links use href="#" without proper handlers, making them inaccessible and causing the page to jump to the top on click.

🔧 Proposed fix: Add proper routes
             <a
-              href="#"
+              href="/terms-of-service"
               className="font-bold text-[`#007B55`] hover:underline"
             >
               Terms of Service
             </a>{' '}
             and{' '}
             <a
-              href="#"
+              href="/privacy-policy"
               className="font-bold text-[`#007B55`] hover:underline"
             >
               Privacy Policy
             </a>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/login/page.tsx` around lines 331 - 337, Links for "Terms of Service"
and "Privacy Policy" are using href="#" which causes jumps and is inaccessible;
replace the dummy anchors with proper routes (e.g., href="/terms" and
href="/privacy") and use Next.js <Link> if available (or regular <a> with real
hrefs) for the "Terms of Service" and "Privacy Policy" elements in the login
page (the anchors containing the text "Terms of Service" and "Privacy Policy");
ensure the className and hover styles are preserved and remove the '#' handlers
so clicks navigate to the correct pages.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants