Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 29 additions & 8 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,10 +1,31 @@
# ─── API ───────────────────────────────────────────
NEXT_PUBLIC_API_URL=http://localhost:5000
# Base URL of the dnb-backend REST API

NEXT_PUBLIC_AI_API_URL=http://localhost:8000
# Base URL of the dnb-ai FastAPI service for the AI assistant

# ─── Stellar ───────────────────────────────────────
NEXT_PUBLIC_STELLAR_NETWORK=testnet
# Stellar network to use: "testnet" or "mainnet"

# ─── Cloudinary ────────────────────────────────────
NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME=your_cloudinary_cloud_name
NEXT_PUBLIC_CLOUDINARY_API_KEY=your_cloudinary_api_key
NEXT_PUBLIC_CLOUDINARY_API_SECRET=your_cloudinary_api_secret
NEXT_PUBLIC_CLOUDINARY_URL=your_cloudinary_url
# Cloudinary cloud name for unsigned uploads (required)

# ─── Jitsi ─────────────────────────────────────────
NEXT_PUBLIC_JITSI_DOMAIN=https://meet.jit.si
# NODE_ENV=production
NEXT_PUBLIC_SOCKET_URL=https://dnb-backend-api.onrender.com
DNB_API_URL=https://dnb-backend-api.onrender.com
NEXT_PUBLIC_API_URL=https://dnb-backend-api.onrender.com
NEXT_PUBLIC_STELLAR_NETWORK=testnet
# Jitsi Meet domain for video spaces
Comment on lines 17 to +18

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Example value includes a protocol prefix that the code doesn't expect.

lib/config/env.js's jitsiDomain getter falls back to the bare domain "meet.jit.si" (no protocol), and the README documents the same bare-domain default. This example uses https://meet.jit.si. If a contributor copies this literally and downstream code builds a URL like https://${jitsiDomain}/... (or passes it to the Jitsi Meet External API, which expects a bare domain), the protocol gets duplicated.

🩹 Proposed fix
-NEXT_PUBLIC_JITSI_DOMAIN=https://meet.jit.si
+NEXT_PUBLIC_JITSI_DOMAIN=meet.jit.si
📝 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
NEXT_PUBLIC_JITSI_DOMAIN=https://meet.jit.si
# NODE_ENV=production
DNB_API_URL=https://dnb-backend-api.onrender.com
NEXT_PUBLIC_API_URL=https://dnb-backend-api.onrender.com
NEXT_PUBLIC_STELLAR_NETWORK=testnet
# Jitsi Meet domain for video spaces
NEXT_PUBLIC_JITSI_DOMAIN=meet.jit.si
# Jitsi Meet domain for video spaces
🤖 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 @.env.example around lines 17 - 18, Update the NEXT_PUBLIC_JITSI_DOMAIN
example value to the bare domain meet.jit.si without the https:// protocol,
matching the jitsiDomain getter default and README documentation.


NEXT_PUBLIC_JITSI_REQUIRE_JWT=false
# Set to "true" if the Jitsi deployment requires a signed JWT token

# ─── Firebase ─────────────────────────────────────
NEXT_PUBLIC_FIREBASE_API_KEY=AIzaSyC8LlmtlWXvbcbyVbdyv4r-tDsGhhukdag
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=deen-bridge-22195.firebaseapp.com
NEXT_PUBLIC_FIREBASE_PROJECT_ID=deen-bridge-22195
NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=deen-bridge-22195.firebasestorage.app
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=368531944242
NEXT_PUBLIC_FIREBASE_APP_ID=1:368531944242:web:7994b11820741a69d35d2b
NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID=G-ZZ81THLVCC
# Firebase Web SDK config values (not secrets, but environment-specific)
17 changes: 11 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,17 @@ The app runs at `http://localhost:3000`.

### Environment Variables

| Variable | Description |
|----------|-------------|
| `NEXT_PUBLIC_API_URL` | Base URL of the [dnb-backend](https://github.com/Deen-Bridge/dnb-backend) API |
| `NEXT_PUBLIC_STELLAR_NETWORK` | `testnet` or `public` (mainnet) |

See `.env.example` for the full list.
| Variable | Required | Description |
|----------|----------|-------------|
| `NEXT_PUBLIC_API_URL` | No (default `http://localhost:5000`) | Base URL of the [dnb-backend](https://github.com/Deen-Bridge/dnb-backend) API |
| `NEXT_PUBLIC_AI_API_URL` | No (default `http://localhost:8000`) | Base URL of the [dnb-ai](https://github.com/Deen-Bridge/dnb-ai) AI assistant service |
| `NEXT_PUBLIC_STELLAR_NETWORK` | No (default `testnet`) | Stellar network — `testnet` or `mainnet` |
| `NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME` | **Yes** | Cloudinary cloud name for unsigned image/video uploads |
| `NEXT_PUBLIC_JITSI_DOMAIN` | No (default `meet.jit.si`) | Jitsi Meet domain for live video spaces |
| `NEXT_PUBLIC_JITSI_REQUIRE_JWT` | No (default `false`) | Whether the Jitsi deployment requires a signed JWT token |
| `NEXT_PUBLIC_FIREBASE_*` | No | Firebase Web SDK config values (apiKey, authDomain, projectId, etc.) — defaults to the project's current values |

See `.env.example` for the full variable list with example values.

### Scripts

Expand Down
5 changes: 2 additions & 3 deletions app/api/ai/chat/route.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import axios from "axios";
import { config } from "@/lib/config/env";

export async function POST(req) {
try {
const { message, chat_id, user_id } = await req.json();
console.log("Sending message to AI:", message);
console.log("Chat ID:", chat_id, "User ID:", user_id);
Comment on lines 7 to 8

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Do not log raw AI prompts or user identifiers.

message, chat_id, and user_id may contain sensitive content or user identifiers. Remove these logs, or redact them through a production-safe structured logger with an explicit retention policy; the full response and error payload logs in this handler should be treated the same way.

🤖 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 `@app/api/ai/chat/route.js` around lines 7 - 8, Remove the raw console logs for
message, chat_id, and user_id in the chat handler, and apply the same
production-safe redaction or removal to full AI response and error payload
logging in the surrounding handler. Do not emit sensitive prompt content or user
identifiers; use the existing structured logger only with explicitly safe,
retention-compliant fields.


// Use environment variable or fallback to localhost
const AI_API_URL =
process.env.NEXT_PUBLIC_AI_API_URL || "http://localhost:8000";
const AI_API_URL = config.aiApiUrl;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Make environment fallbacks development-only. The centralized configuration currently allows missing production values to resolve to localhost or testnet, silently targeting the wrong service or Stellar network instead of failing startup.

  • app/api/ai/chat/route.js#L10-L10: require NEXT_PUBLIC_AI_API_URL in production.
  • app/api/ai/stream/route.js#L14-L14: apply the same production requirement to streaming requests.
  • components/organisms/dashboard/ai/Ai-Sidebar.jsx#L14-L14: prevent browser requests from falling back to the user’s localhost.
  • lib/actions/ai/load-chat-history.js#L12-L12: require the AI URL for chat-history loading.
  • lib/actions/ai/load-chat-history.js#L36-L36: require the AI URL for message-history loading.
  • app/api/books/[bookId]/preview/route.js#L5-L6: require NEXT_PUBLIC_API_URL before constructing the proxy URL.
  • lib/actions/cached-api.js#L9-L9: prevent cached API calls from using localhost in production.
  • lib/config/axios.config.js#L4-L4: prevent Axios from initializing against localhost in production.
  • components/stellar/StellarProvider.jsx#L19-L21: require NEXT_PUBLIC_STELLAR_NETWORK in production instead of defaulting to testnet.
📍 Affects 8 files
  • app/api/ai/chat/route.js#L10-L10 (this comment)
  • app/api/ai/stream/route.js#L14-L14
  • components/organisms/dashboard/ai/Ai-Sidebar.jsx#L14-L14
  • lib/actions/ai/load-chat-history.js#L12-L12
  • lib/actions/ai/load-chat-history.js#L36-L36
  • app/api/books/[bookId]/preview/route.js#L5-L6
  • lib/actions/cached-api.js#L9-L9
  • lib/config/axios.config.js#L4-L4
  • components/stellar/StellarProvider.jsx#L19-L21
🤖 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 `@app/api/ai/chat/route.js` at line 10, Make configuration fallbacks
development-only: in app/api/ai/chat/route.js:10, app/api/ai/stream/route.js:14,
components/organisms/dashboard/ai/Ai-Sidebar.jsx:14,
lib/actions/ai/load-chat-history.js:12 and :36,
app/api/books/[bookId]/preview/route.js:5-6, lib/actions/cached-api.js:9, and
lib/config/axios.config.js:4 require the respective AI/API URL in production
instead of falling back to localhost; in
components/stellar/StellarProvider.jsx:19-21 require NEXT_PUBLIC_STELLAR_NETWORK
in production instead of defaulting to testnet. Preserve the existing
development fallbacks.

const endpoint = `${AI_API_URL}/chat`;

console.log("Using AI API URL:", endpoint);
Expand Down
5 changes: 2 additions & 3 deletions app/api/ai/stream/route.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@

import { config } from "@/lib/config/env";

export async function POST(req) {
try {
Expand All @@ -10,9 +11,7 @@ export async function POST(req) {
user_id,
});

// Use environment variable or fallback to localhost
const AI_API_URL =
process.env.NEXT_PUBLIC_AI_API_URL || "http://localhost:8000";
const AI_API_URL = config.aiApiUrl;
const endpoint = `${AI_API_URL}/chat/stream`;

// Make request to AI backend
Expand Down
4 changes: 2 additions & 2 deletions app/api/books/[bookId]/preview/route.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { NextResponse } from "next/server";
import { cookies } from "next/headers";
import { config } from "@/lib/config/env";

const getBackendBaseUrl = () => {
const base = process.env.NEXT_PUBLIC_API_URL || "http://localhost:5000";
return base.replace(/\/$/, "");
return config.apiUrl.replace(/\/$/, "");
};

export async function GET(_request, { params }) {
Expand Down
10 changes: 4 additions & 6 deletions components/organisms/dashboard/JaasMeetingClientSection.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import JaasMeetingComponent from "@/components/organisms/jitsi/JitsiMeeting";
import { joinSpaceWaitlist } from "@/lib/actions/spaces/joinSpaceWaitlist";
import { updateSpace } from "@/lib/actions/spaces/updateSpace";
import { getSpaceMeetingToken } from "@/lib/actions/calls/get-space-meeting-token";
import { config } from "@/lib/config/env";

const normalizeDomain = (domain = "meet.jit.si") =>
domain.replace(/^https?:\/\//i, "").replace(/\/+$/g, "");
Expand Down Expand Up @@ -39,9 +40,7 @@ export default function JaasMeetingClientButtons({ space }) {
const [isCopying, setIsCopying] = useState(false);
const [joinLoading, setJoinLoading] = useState(false);
const [tokenLoading, setTokenLoading] = useState(false);
const envRequiresJwt =
typeof process !== "undefined" &&
process.env.NEXT_PUBLIC_JITSI_REQUIRE_JWT === "true";
const envRequiresJwt = config.jitsiRequireJwt;

const [meetingToken, setMeetingToken] = useState(null);
const [requiresJwt, setRequiresJwt] = useState(
Expand All @@ -52,8 +51,7 @@ export default function JaasMeetingClientButtons({ space }) {
[requiresJwt, envRequiresJwt]
);
const [meetingMeta, setMeetingMeta] = useState(() => {
const domain =
process.env.NEXT_PUBLIC_JITSI_DOMAIN || "meet.jit.si";
const domain = config.jitsiDomain;
const normalizedDomain = normalizeDomain(domain);
Comment on lines +54 to 55

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Treat a blank Jitsi domain as unset.

The shared schema accepts "", and config.jitsiDomain uses a nullish fallback, so an empty NEXT_PUBLIC_JITSI_DOMAIN reaches normalizeDomain(""). That can produce https:///... meeting URLs. Trim/reject blank values or preserve the previous || "meet.jit.si" fallback in the configuration module.

Also applies to: 82-84

🤖 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 `@components/organisms/dashboard/JaasMeetingClientSection.jsx` around lines 54
- 55, Update the Jitsi domain configuration flow used by
JaasMeetingClientSection so blank or whitespace-only NEXT_PUBLIC_JITSI_DOMAIN
values are treated as unset before normalizeDomain receives them. Use the
existing default-domain fallback (such as meet.jit.si) or trim and reject blank
values in the configuration module, covering both referenced domain usages.

const fallbackRoom =
space?.meetingRoom || (space?._id ? `deenbridge-space-${space._id}` : "");
Expand Down Expand Up @@ -82,7 +80,7 @@ export default function JaasMeetingClientButtons({ space }) {
}, [waitListIds, user?._id]);

const baseDomain = useMemo(
() => normalizeDomain(process.env.NEXT_PUBLIC_JITSI_DOMAIN || "meet.jit.si"),
() => normalizeDomain(config.jitsiDomain),
[]
);

Expand Down
4 changes: 2 additions & 2 deletions components/organisms/dashboard/ai/Ai-Sidebar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ import { Plus, Trash2, MessageSquare } from "lucide-react";
import { toast } from "sonner";
import { formatDistanceToNow } from "date-fns";
import Button from "@/components/atoms/form/Button";
import { config } from "@/lib/config/env";

export function AiSidebar({ onChatSelect, currentChatId, onNewChat }) {
const [chatHistory, setChatHistory] = useState([]);
const [loadingHistory, setLoadingHistory] = useState(false);
const [userId, setUserId] = useState(null);
const AI_API_URL =
process.env.NEXT_PUBLIC_AI_API_URL || "http://localhost:8000";
const AI_API_URL = config.aiApiUrl;

useEffect(() => {
const loadUserChats = async () => {
Expand Down
3 changes: 2 additions & 1 deletion components/stellar/StellarProvider.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ import { AlbedoModule } from "@creit.tech/stellar-wallets-kit/modules/albedo";
import { toast } from "sonner";
import useAuth from "@/hooks/useAuth";
import axiosInstance from "@/lib/config/axios.config";
import { config } from "@/lib/config/env";

const NETWORK = process.env.NEXT_PUBLIC_STELLAR_NETWORK || "testnet";
const NETWORK = config.stellarNetwork;

const StellarContext = createContext(null);

Expand Down
7 changes: 3 additions & 4 deletions lib/actions/ai/load-chat-history.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { cachedFetch, cacheKeys, CACHE_CONFIG } from "@/lib/utils/cache";
import { config } from "@/lib/config/env";

/**
* Load chat history for a specific user
Expand All @@ -8,8 +9,7 @@ import { cachedFetch, cacheKeys, CACHE_CONFIG } from "@/lib/utils/cache";
*/
export const loadChatHistory = async (userId, forceRefresh = false) => {
try {
const AI_API_URL =
process.env.NEXT_PUBLIC_AI_API_URL || "http://localhost:8000";
const AI_API_URL = config.aiApiUrl;

const data = await cachedFetch(`${AI_API_URL}/user/${userId}/chats`, {
cacheKey: cacheKeys.chatHistory(userId),
Expand All @@ -33,8 +33,7 @@ export const loadChatHistory = async (userId, forceRefresh = false) => {
*/
export const loadChatMessages = async (chatId, forceRefresh = false) => {
try {
const AI_API_URL =
process.env.NEXT_PUBLIC_AI_API_URL || "http://localhost:8000";
const AI_API_URL = config.aiApiUrl;

const history = await cachedFetch(`${AI_API_URL}/chat/${chatId}/history`, {
cacheKey: cacheKeys.chatMessages(chatId),
Expand Down
3 changes: 2 additions & 1 deletion lib/actions/cached-api.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import {
CACHE_CONFIG,
smartCache,
} from "@/lib/utils/cache";
import { config } from "@/lib/config/env";

const API_URL = process.env.NEXT_PUBLIC_API_URL || "http://localhost:5000";
const API_URL = config.apiUrl;

/**
* Courses API with caching
Expand Down
12 changes: 2 additions & 10 deletions lib/config/axios.config.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,7 @@
import axios from "axios";
import { config } from "@/lib/config/env";

const isDev = process.env.NODE_ENV === "development";

// Use environment variable if set, otherwise fallback to defaults
const baseURL =
process.env.NEXT_PUBLIC_API_URL ||
(isDev ? "http://localhost:5000" : "https://dnb-backend-api.onrender.com");

console.log(
`🌐 API Base URL: ${baseURL} (${isDev ? "Development" : "Production"} mode)`
);
const baseURL = config.apiUrl;

const axiosInstance = axios.create({
baseURL,
Expand Down
132 changes: 132 additions & 0 deletions lib/config/env.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import { z } from "zod";

function warn(field, fallback) {
if (process.env.NODE_ENV !== "production") {
console.warn(
`\u26a0\ufe0f ${field} is not set. Using "${fallback}" as fallback.`
);
}
}

const dangerous = Object.keys(process.env).filter((k) =>
/^NEXT_PUBLIC_.*SECRET/.test(k)
);

if (dangerous.length > 0) {
throw new Error(
`\u274c Dangerous environment variable(s) detected: ${dangerous.join(", ")}.\n` +
`Variables prefixed with NEXT_PUBLIC_ are exposed to the browser bundle and must never contain secrets.\n` +
`Rename the variable(s) to remove the NEXT_PUBLIC_ prefix.`
);
}

const raw = {
NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL,
NEXT_PUBLIC_AI_API_URL: process.env.NEXT_PUBLIC_AI_API_URL,
NEXT_PUBLIC_STELLAR_NETWORK: process.env.NEXT_PUBLIC_STELLAR_NETWORK,
NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME:
process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME,
NEXT_PUBLIC_JITSI_DOMAIN: process.env.NEXT_PUBLIC_JITSI_DOMAIN,
NEXT_PUBLIC_JITSI_REQUIRE_JWT: process.env.NEXT_PUBLIC_JITSI_REQUIRE_JWT,
NEXT_PUBLIC_FIREBASE_API_KEY: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN:
process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
NEXT_PUBLIC_FIREBASE_PROJECT_ID:
process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET:
process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID:
process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
NEXT_PUBLIC_FIREBASE_APP_ID: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,
NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID:
process.env.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID,
};

const envSchema = z.object({
NEXT_PUBLIC_API_URL: z.string().url().optional(),
NEXT_PUBLIC_AI_API_URL: z.string().url().optional(),
NEXT_PUBLIC_STELLAR_NETWORK: z.enum(["testnet", "mainnet"]).optional(),
NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME: z
.string()
.min(1, "Cloudinary cloud name is required"),
NEXT_PUBLIC_JITSI_DOMAIN: z.string().optional(),
NEXT_PUBLIC_JITSI_REQUIRE_JWT: z.enum(["true", "false"]).optional(),
NEXT_PUBLIC_FIREBASE_API_KEY: z.string().optional(),
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN: z.string().optional(),
NEXT_PUBLIC_FIREBASE_PROJECT_ID: z.string().optional(),
NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET: z.string().optional(),
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID: z.string().optional(),
NEXT_PUBLIC_FIREBASE_APP_ID: z.string().optional(),
NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID: z.string().optional(),
});

const parsed = envSchema.safeParse(raw);

if (!parsed.success) {
const issues = parsed.error.errors.map((e) => {
const path = e.path.join(".");
return ` - ${path}: ${e.message}`;
});
throw new Error(
`\u274c Environment configuration is invalid:\n${issues.join("\n")}\n\nPlease check your .env.local file.`
);
}

const env = parsed.data;

export const config = Object.freeze({
get apiUrl() {
return (
env.NEXT_PUBLIC_API_URL ??
(warn("NEXT_PUBLIC_API_URL", "http://localhost:5000"),
"http://localhost:5000")
);
},

get aiApiUrl() {
return (
env.NEXT_PUBLIC_AI_API_URL ??
(warn("NEXT_PUBLIC_AI_API_URL", "http://localhost:8000"),
"http://localhost:8000")
);
},

get stellarNetwork() {
return env.NEXT_PUBLIC_STELLAR_NETWORK ?? "testnet";
},

get cloudinaryCloudName() {
return env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME;
},

get jitsiDomain() {
return env.NEXT_PUBLIC_JITSI_DOMAIN ?? "meet.jit.si";
},

get jitsiRequireJwt() {
return env.NEXT_PUBLIC_JITSI_REQUIRE_JWT === "true";
},

get firebase() {
return {
apiKey:
env.NEXT_PUBLIC_FIREBASE_API_KEY ??
"AIzaSyC8LlmtlWXvbcbyVbdyv4r-tDsGhhukdag",
authDomain:
env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN ??
"deen-bridge-22195.firebaseapp.com",
projectId:
env.NEXT_PUBLIC_FIREBASE_PROJECT_ID ?? "deen-bridge-22195",
storageBucket:
env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET ??
"deen-bridge-22195.firebasestorage.app",
messagingSenderId:
env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID ?? "368531944242",
appId:
env.NEXT_PUBLIC_FIREBASE_APP_ID ??
"1:368531944242:web:7994b11820741a69d35d2b",
measurementId:
env.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID ?? "G-ZZ81THLVCC",
};
},
Comment on lines +76 to +132

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Fallback warnings are inconsistent across getters.

Only apiUrl/aiApiUrl call warn() when falling back to a default (lines 80-83, 88-91). stellarNetwork (95), jitsiDomain (103), jitsiRequireJwt (107), and every field in firebase (112-129) silently use hardcoded defaults with no warning. This directly conflicts with the stated goal to "preserve sensible development fallbacks while warning when they are used" — a dev could unknowingly run against the wrong Stellar network or a stale Firebase project with zero console signal.

♻️ Suggested refactor: unify via a shared helper
+function withFallback(field, value, fallback) {
+  if (value === undefined) {
+    warn(field, fallback);
+    return fallback;
+  }
+  return value;
+}
+
 export const config = Object.freeze({
   get apiUrl() {
-    return (
-      env.NEXT_PUBLIC_API_URL ??
-      (warn("NEXT_PUBLIC_API_URL", "http://localhost:5000"),
-      "http://localhost:5000")
-    );
+    return withFallback("NEXT_PUBLIC_API_URL", env.NEXT_PUBLIC_API_URL, "http://localhost:5000");
   },
   get aiApiUrl() {
-    return (
-      env.NEXT_PUBLIC_AI_API_URL ??
-      (warn("NEXT_PUBLIC_AI_API_URL", "http://localhost:8000"),
-      "http://localhost:8000")
-    );
+    return withFallback("NEXT_PUBLIC_AI_API_URL", env.NEXT_PUBLIC_AI_API_URL, "http://localhost:8000");
   },
   get stellarNetwork() {
-    return env.NEXT_PUBLIC_STELLAR_NETWORK ?? "testnet";
+    return withFallback("NEXT_PUBLIC_STELLAR_NETWORK", env.NEXT_PUBLIC_STELLAR_NETWORK, "testnet");
   },
   get jitsiDomain() {
-    return env.NEXT_PUBLIC_JITSI_DOMAIN ?? "meet.jit.si";
+    return withFallback("NEXT_PUBLIC_JITSI_DOMAIN", env.NEXT_PUBLIC_JITSI_DOMAIN, "meet.jit.si");
   },
   get jitsiRequireJwt() {
-    return env.NEXT_PUBLIC_JITSI_REQUIRE_JWT === "true";
+    return withFallback("NEXT_PUBLIC_JITSI_REQUIRE_JWT", env.NEXT_PUBLIC_JITSI_REQUIRE_JWT, "false") === "true";
   },
   get firebase() {
     return {
       apiKey:
-        env.NEXT_PUBLIC_FIREBASE_API_KEY ??
-        "AIzaSyC8LlmtlWXvbcbyVbdyv4r-tDsGhhukdag",
+        withFallback("NEXT_PUBLIC_FIREBASE_API_KEY", env.NEXT_PUBLIC_FIREBASE_API_KEY, "AIzaSyC8LlmtlWXvbcbyVbdyv4r-tDsGhhukdag"),
       // ...apply the same pattern to authDomain, projectId, storageBucket,
       // messagingSenderId, appId, measurementId
     };
   },
 });

This also removes the repeated comma-operator idiom, which is easy to misread.

🧰 Tools
🪛 Betterleaks (1.7.0)

[high] 114-114: Uncovered a GCP API key, which could lead to unauthorized access to Google Cloud services and data breaches.

(gcp-api-key)

🤖 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 `@lib/config/env.js` around lines 78 - 131, Update the configuration getters
stellarNetwork, jitsiDomain, jitsiRequireJwt, and firebase so every hardcoded
fallback invokes the existing warn helper with its environment key and fallback
value before returning that value. Preserve the current defaults and boolean
behavior, while replacing the repeated comma-operator pattern with the shared
fallback approach if available.

Comment on lines +111 to +132

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Real Firebase Web API key/project identifiers hardcoded and duplicated across two files. The same live Firebase credentials appear both as source-code fallback defaults and as ".env.example" values, and Betterleaks independently flags both sites as a leaked GCP API key. As per path instructions, **/*.{js,jsx} files must "Flag hardcoded secrets, API keys, or wallet secret keys," so even though Firebase Web API keys aren't traditionally treated as high-secrecy secrets by Google, baking the actual project's live values into shipped code (rather than a placeholder) increases exposure surface and makes rotation/tracking harder.

  • lib/config/env.js#L110-L131: don't hardcode the real key/IDs as JS fallback defaults — require NEXT_PUBLIC_FIREBASE_* to be explicitly set (with a clear startup error if missing) instead of silently defaulting to production-identifying values baked into the bundle.
  • .env.example#L24-L30: replace the real project values with generic placeholders (e.g. your_firebase_api_key), consistent with how NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME already uses a placeholder in this file.

Since these values are already committed to git history, also consider rotating/restricting the Firebase Web API key (HTTP referrer + API restrictions in Google Cloud Console) as a follow-up, independent of this code change.

🧰 Tools
🪛 Betterleaks (1.7.0)

[high] 114-114: Uncovered a GCP API key, which could lead to unauthorized access to Google Cloud services and data breaches.

(gcp-api-key)

📍 Affects 2 files
  • lib/config/env.js#L110-L131 (this comment)
  • .env.example#L24-L30
🤖 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 `@lib/config/env.js` around lines 110 - 131, Remove the live Firebase values
from the firebase configuration getter in lib/config/env.js, require every
NEXT_PUBLIC_FIREBASE_* variable explicitly, and raise a clear startup error when
any is missing. Replace the real Firebase values with generic placeholders in
.env.example lines 24-30; rotating or restricting the previously committed key
is a separate follow-up.

Sources: Path instructions, Linters/SAST tools

});
11 changes: 2 additions & 9 deletions lib/config/firebase.config.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,8 @@
import { initializeApp } from "firebase/app";
import { getFirestore } from "firebase/firestore";
import { config } from "@/lib/config/env";

const firebaseConfig = {
apiKey: "AIzaSyC8LlmtlWXvbcbyVbdyv4r-tDsGhhukdag",
authDomain: "deen-bridge-22195.firebaseapp.com",
projectId: "deen-bridge-22195",
storageBucket: "deen-bridge-22195.firebasestorage.app",
messagingSenderId: "368531944242",
appId: "1:368531944242:web:7994b11820741a69d35d2b",
measurementId: "G-ZZ81THLVCC",
};
const firebaseConfig = config.firebase;

const app = initializeApp(firebaseConfig);
const db = getFirestore(app);
Expand Down
Loading