Skip to content

Commit 8c311a2

Browse files
committed
demo mode update
1 parent ea2ea9c commit 8c311a2

4 files changed

Lines changed: 144 additions & 112 deletions

File tree

LYNX/server/server.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ try {
3131
APP_VERSION = pkg.version || APP_VERSION;
3232
} catch { /* package.json not available; use the fallback */ }
3333

34+
const DEMO_MODE = String(process.env.DEMO_MODE || '').toLowerCase() === 'true' || process.env.DEMO_MODE === '1';
35+
console.log('Demo mode:', DEMO_MODE);
36+
3437
// DATA_DIR is set to /app/data in Docker (see Dockerfile ENV).
3538
// When running locally without the env var, data lives next to server.js.
3639
const DATA_DIR = process.env.DATA_DIR || __dirname;
@@ -857,6 +860,10 @@ app.post('/api/validate-password', (req, res) => {
857860
});
858861

859862
app.post('/api/auth/change-password', authLimiter, authenticateToken, async (req, res) => {
863+
if (DEMO_MODE) {
864+
return res.status(403).json({ success: false, error: 'Change password is disabled in demo mode.' });
865+
}
866+
860867
try {
861868
const { currentPassword, newPassword } = req.body || {};
862869

@@ -916,6 +923,10 @@ app.post('/api/auth/change-password', authLimiter, authenticateToken, async (req
916923

917924
// Password reset via RESET_TOKEN env var
918925
app.post('/api/auth/reset-via-token', resetLimiter, async (req, res) => {
926+
if (DEMO_MODE) {
927+
return res.status(403).json({ success: false, error: 'Password reset is disabled in demo mode.' });
928+
}
929+
919930
try {
920931
const { token, newPassword } = req.body || {};
921932
const resetToken = process.env.RESET_TOKEN;
@@ -1250,6 +1261,7 @@ app.get('/health', (req, res) => {
12501261
res.json({
12511262
status: 'ok',
12521263
version: APP_VERSION,
1264+
demoMode: DEMO_MODE,
12531265
timestamp: new Date().toISOString(),
12541266
uptime: Math.floor(process.uptime()),
12551267
node: process.version,

LYNX/src/components/PasswordManager.tsx

Lines changed: 123 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { Card } from "@/components/ui/card";
55
import { Label } from "@/components/ui/label";
66
import { Eye, EyeOff, Key, CheckCircle, AlertTriangle, Shield } from "lucide-react";
77
import { isPasswordStrong } from "@/lib/auth";
8+
import { DEMO_MODE } from "@/lib/config";
89
import { authApi } from "@/lib/api-client";
910

1011
type MessageType = 'success' | 'error' | 'info' | 'warning';
@@ -38,6 +39,7 @@ export const PasswordManager = () => {
3839
const [resetNewPassword, setResetNewPassword] = useState("");
3940
const [tokenResetMessage, setTokenResetMessage] = useState<Message | null>(null);
4041
const [tokenResetLoading, setTokenResetLoading] = useState(false);
42+
const demoMode = DEMO_MODE;
4143

4244
const handleSubmit = async (e: React.FormEvent) => {
4345
e.preventDefault();
@@ -207,119 +209,126 @@ export const PasswordManager = () => {
207209
</p>
208210
</div>
209211

210-
<form onSubmit={handleSubmit} className="space-y-4">
211-
<div className="space-y-2">
212-
<Label htmlFor="current-password">Current Password</Label>
213-
<div className="relative">
214-
<Input
215-
id="current-password"
216-
type={showCurrentPassword ? "text" : "password"}
217-
value={currentPassword}
218-
onChange={(e) => setCurrentPassword(e.target.value)}
219-
className="glass-card border-primary/20 pr-10"
220-
placeholder="Enter current password"
221-
required
222-
disabled={isLoading}
223-
/>
224-
<Button
225-
type="button"
226-
variant="ghost"
227-
size="icon"
228-
className="absolute right-0 top-0 h-full px-3 hover:bg-transparent"
229-
onClick={() => setShowCurrentPassword(!showCurrentPassword)}
230-
disabled={isLoading}
231-
>
232-
{showCurrentPassword ? <EyeOff className="w-4 h-4" /> : <Eye className="w-4 h-4" />}
233-
</Button>
234-
</div>
212+
{demoMode ? (
213+
<div className="rounded-2xl border border-yellow-300 bg-yellow-50 p-4 text-sm text-yellow-900">
214+
<p className="font-semibold">Demo mode is active</p>
215+
<p className="mt-1">Password change and password reset are disabled in demo mode.</p>
235216
</div>
236-
237-
<div className="space-y-2">
238-
<Label htmlFor="new-password">New Password</Label>
239-
<div className="relative">
240-
<Input
241-
id="new-password"
242-
type={showNewPassword ? "text" : "password"}
243-
value={newPassword}
244-
onChange={(e) => setNewPassword(e.target.value)}
245-
className="glass-card border-primary/20 pr-10"
246-
placeholder="Enter new password"
247-
required
248-
disabled={isLoading}
249-
/>
250-
<Button
251-
type="button"
252-
variant="ghost"
253-
size="icon"
254-
className="absolute right-0 top-0 h-full px-3 hover:bg-transparent"
255-
onClick={() => setShowNewPassword(!showNewPassword)}
256-
disabled={isLoading}
257-
>
258-
{showNewPassword ? <EyeOff className="w-4 h-4" /> : <Eye className="w-4 h-4" />}
259-
</Button>
260-
</div>
261-
<div className="text-xs text-muted-foreground space-y-1">
262-
<p>Requirements:</p>
263-
<ul className="list-disc list-inside ml-2 space-y-0.5">
264-
<li className={newPassword.length >= 8 ? 'text-green-400' : ''}>At least 8 characters</li>
265-
<li className={/[A-Z]/.test(newPassword) ? 'text-green-400' : ''}>Uppercase letter</li>
266-
<li className={/[a-z]/.test(newPassword) ? 'text-green-400' : ''}>Lowercase letter</li>
267-
<li className={/\d/.test(newPassword) ? 'text-green-400' : ''}>Number</li>
268-
<li className={/[!@#$%^&*(),.?":{}|<>]/.test(newPassword) ? 'text-green-400' : ''}>Special character</li>
269-
</ul>
217+
) : (
218+
<form onSubmit={handleSubmit} className="space-y-4">
219+
<div className="space-y-2">
220+
<Label htmlFor="current-password">Current Password</Label>
221+
<div className="relative">
222+
<Input
223+
id="current-password"
224+
type={showCurrentPassword ? "text" : "password"}
225+
value={currentPassword}
226+
onChange={(e) => setCurrentPassword(e.target.value)}
227+
className="glass-card border-primary/20 pr-10"
228+
placeholder="Enter current password"
229+
required
230+
disabled={isLoading}
231+
/>
232+
<Button
233+
type="button"
234+
variant="ghost"
235+
size="icon"
236+
className="absolute right-0 top-0 h-full px-3 hover:bg-transparent"
237+
onClick={() => setShowCurrentPassword(!showCurrentPassword)}
238+
disabled={isLoading}
239+
>
240+
{showCurrentPassword ? <EyeOff className="w-4 h-4" /> : <Eye className="w-4 h-4" />}
241+
</Button>
242+
</div>
270243
</div>
271-
</div>
272244

273-
<div className="space-y-2">
274-
<Label htmlFor="confirm-password">Confirm New Password</Label>
275-
<div className="relative">
276-
<Input
277-
id="confirm-password"
278-
type={showConfirmPassword ? "text" : "password"}
279-
value={confirmPassword}
280-
onChange={(e) => setConfirmPassword(e.target.value)}
281-
className="glass-card border-primary/20 pr-10"
282-
placeholder="Confirm new password"
283-
required
284-
disabled={isLoading}
285-
/>
286-
<Button
287-
type="button"
288-
variant="ghost"
289-
size="icon"
290-
className="absolute right-0 top-0 h-full px-3 hover:bg-transparent"
291-
onClick={() => setShowConfirmPassword(!showConfirmPassword)}
292-
disabled={isLoading}
293-
>
294-
{showConfirmPassword ? <EyeOff className="w-4 h-4" /> : <Eye className="w-4 h-4" />}
295-
</Button>
245+
<div className="space-y-2">
246+
<Label htmlFor="new-password">New Password</Label>
247+
<div className="relative">
248+
<Input
249+
id="new-password"
250+
type={showNewPassword ? "text" : "password"}
251+
value={newPassword}
252+
onChange={(e) => setNewPassword(e.target.value)}
253+
className="glass-card border-primary/20 pr-10"
254+
placeholder="Enter new password"
255+
required
256+
disabled={isLoading}
257+
/>
258+
<Button
259+
type="button"
260+
variant="ghost"
261+
size="icon"
262+
className="absolute right-0 top-0 h-full px-3 hover:bg-transparent"
263+
onClick={() => setShowNewPassword(!showNewPassword)}
264+
disabled={isLoading}
265+
>
266+
{showNewPassword ? <EyeOff className="w-4 h-4" /> : <Eye className="w-4 h-4" />}
267+
</Button>
268+
</div>
269+
<div className="text-xs text-muted-foreground space-y-1">
270+
<p>Requirements:</p>
271+
<ul className="list-disc list-inside ml-2 space-y-0.5">
272+
<li className={newPassword.length >= 8 ? 'text-green-400' : ''}>At least 8 characters</li>
273+
<li className={/[A-Z]/.test(newPassword) ? 'text-green-400' : ''}>Uppercase letter</li>
274+
<li className={/[a-z]/.test(newPassword) ? 'text-green-400' : ''}>Lowercase letter</li>
275+
<li className={/\d/.test(newPassword) ? 'text-green-400' : ''}>Number</li>
276+
<li className={/[!@#$%^&*(),.?":{}|<>]/.test(newPassword) ? 'text-green-400' : ''}>Special character</li>
277+
</ul>
278+
</div>
296279
</div>
297-
</div>
298280

299-
{message && (
300-
<div className={`text-sm p-3 rounded-lg flex items-center gap-2 ${
301-
message.type === 'success'
302-
? 'bg-green-500/10 text-green-400 border border-green-500/20'
303-
: 'bg-destructive/10 text-destructive border border-destructive/20'
304-
}`}>
305-
{message.type === 'success' ? (
306-
<CheckCircle className="w-4 h-4" />
307-
) : (
308-
<AlertTriangle className="w-4 h-4" />
309-
)}
310-
{message.text}
281+
<div className="space-y-2">
282+
<Label htmlFor="confirm-password">Confirm New Password</Label>
283+
<div className="relative">
284+
<Input
285+
id="confirm-password"
286+
type={showConfirmPassword ? "text" : "password"}
287+
value={confirmPassword}
288+
onChange={(e) => setConfirmPassword(e.target.value)}
289+
className="glass-card border-primary/20 pr-10"
290+
placeholder="Confirm new password"
291+
required
292+
disabled={isLoading}
293+
/>
294+
<Button
295+
type="button"
296+
variant="ghost"
297+
size="icon"
298+
className="absolute right-0 top-0 h-full px-3 hover:bg-transparent"
299+
onClick={() => setShowConfirmPassword(!showConfirmPassword)}
300+
disabled={isLoading}
301+
>
302+
{showConfirmPassword ? <EyeOff className="w-4 h-4" /> : <Eye className="w-4 h-4" />}
303+
</Button>
304+
</div>
311305
</div>
312-
)}
313306

314-
<Button
315-
type="submit"
316-
variant="gradient"
317-
className="w-full"
318-
disabled={isLoading}
319-
>
320-
{isLoading ? "Changing Password..." : "Change Password"}
321-
</Button>
322-
</form>
307+
{message && (
308+
<div className={`text-sm p-3 rounded-lg flex items-center gap-2 ${
309+
message.type === 'success'
310+
? 'bg-green-500/10 text-green-400 border border-green-500/20'
311+
: 'bg-destructive/10 text-destructive border border-destructive/20'
312+
}`}>
313+
{message.type === 'success' ? (
314+
<CheckCircle className="w-4 h-4" />
315+
) : (
316+
<AlertTriangle className="w-4 h-4" />
317+
)}
318+
{message.text}
319+
</div>
320+
)}
321+
322+
<Button
323+
type="submit"
324+
variant="gradient"
325+
className="w-full"
326+
disabled={isLoading}
327+
>
328+
{isLoading ? "Changing Password..." : "Change Password"}
329+
</Button>
330+
</form>
331+
)}
323332

324333
<div className="pt-4 border-t border-primary/20">
325334
<div className="bg-destructive/10 border border-destructive/20 rounded-lg p-4 space-y-3">
@@ -343,12 +352,13 @@ export const PasswordManager = () => {
343352
</div>
344353
</Card>
345354
{/* Forgot password — token-based reset */}
346-
<Card className="glass-card p-6 space-y-4">
347-
<button
348-
type="button"
349-
className="w-full text-left flex items-center justify-between"
350-
onClick={() => setShowTokenReset(v => !v)}
351-
>
355+
{!demoMode && (
356+
<Card className="glass-card p-6 space-y-4">
357+
<button
358+
type="button"
359+
className="w-full text-left flex items-center justify-between"
360+
onClick={() => setShowTokenReset(v => !v)}
361+
>
352362
<span className="text-sm font-medium text-muted-foreground">Forgot your password?</span>
353363
<span className="text-xs text-primary">{showTokenReset ? 'Hide' : 'Show'}</span>
354364
</button>
@@ -403,6 +413,7 @@ export const PasswordManager = () => {
403413
</form>
404414
)}
405415
</Card>
416+
)}
406417
</div>
407418
);
408419
};

LYNX/src/lib/config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const DEMO_MODE = import.meta.env.VITE_DEMO_MODE === 'true' || import.meta.env.VITE_DEMO_MODE === '1';

LYNX/src/vite-env.d.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
11
/// <reference types="vite/client" />
22

33
declare const __APP_VERSION__: string;
4+
5+
interface ImportMetaEnv {
6+
readonly VITE_DEMO_MODE?: string;
7+
}
8+
9+
interface ImportMeta {
10+
readonly env: ImportMetaEnv;
11+
}

0 commit comments

Comments
 (0)