@aion0/forge 0.2.31 → 0.2.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app/login/page.tsx +16 -0
- package/bin/forge-server.mjs +62 -0
- package/components/SettingsModal.tsx +3 -0
- package/package.json +1 -1
package/app/login/page.tsx
CHANGED
|
@@ -8,6 +8,7 @@ export default function LoginPage() {
|
|
|
8
8
|
const [sessionCode, setSessionCode] = useState('');
|
|
9
9
|
const [error, setError] = useState('');
|
|
10
10
|
const [isRemote, setIsRemote] = useState(false);
|
|
11
|
+
const [showHelp, setShowHelp] = useState(false);
|
|
11
12
|
|
|
12
13
|
useEffect(() => {
|
|
13
14
|
const host = window.location.hostname;
|
|
@@ -76,6 +77,21 @@ export default function LoginPage() {
|
|
|
76
77
|
Session code is generated when tunnel starts. Use /tunnel_code in Telegram to get it.
|
|
77
78
|
</p>
|
|
78
79
|
)}
|
|
80
|
+
<div className="text-center">
|
|
81
|
+
<button
|
|
82
|
+
type="button"
|
|
83
|
+
onClick={() => setShowHelp(v => !v)}
|
|
84
|
+
className="text-[10px] text-[var(--text-secondary)] hover:text-[var(--text-primary)]"
|
|
85
|
+
>
|
|
86
|
+
Forgot password?
|
|
87
|
+
</button>
|
|
88
|
+
{showHelp && (
|
|
89
|
+
<p className="text-[10px] text-[var(--text-secondary)] mt-1 bg-[var(--bg-tertiary)] rounded p-2">
|
|
90
|
+
Run in terminal:<br />
|
|
91
|
+
<code className="text-[var(--accent)]">forge server start --reset-password</code>
|
|
92
|
+
</p>
|
|
93
|
+
)}
|
|
94
|
+
</div>
|
|
79
95
|
</form>
|
|
80
96
|
|
|
81
97
|
</div>
|
package/bin/forge-server.mjs
CHANGED
|
@@ -59,6 +59,7 @@ const isStop = process.argv.includes('--stop');
|
|
|
59
59
|
const isRestart = process.argv.includes('--restart');
|
|
60
60
|
const isRebuild = process.argv.includes('--rebuild');
|
|
61
61
|
const resetTerminal = process.argv.includes('--reset-terminal');
|
|
62
|
+
const resetPassword = process.argv.includes('--reset-password');
|
|
62
63
|
|
|
63
64
|
const webPort = parseInt(getArg('--port')) || 3000;
|
|
64
65
|
const terminalPort = parseInt(getArg('--terminal-port')) || 3001;
|
|
@@ -89,6 +90,67 @@ process.env.PORT = String(webPort);
|
|
|
89
90
|
process.env.TERMINAL_PORT = String(terminalPort);
|
|
90
91
|
process.env.FORGE_DATA_DIR = DATA_DIR;
|
|
91
92
|
|
|
93
|
+
// ── Password setup (first run or --reset-password) ──
|
|
94
|
+
if (!isStop) {
|
|
95
|
+
const YAML = await import('yaml');
|
|
96
|
+
const settingsFile = join(DATA_DIR, 'settings.yaml');
|
|
97
|
+
let settings = {};
|
|
98
|
+
try { settings = YAML.parse(readFileSync(settingsFile, 'utf-8')) || {}; } catch {}
|
|
99
|
+
|
|
100
|
+
const hasPassword = !!settings.telegramTunnelPassword;
|
|
101
|
+
|
|
102
|
+
if (resetPassword || !hasPassword) {
|
|
103
|
+
if (resetPassword) {
|
|
104
|
+
console.log('[forge] Password reset requested');
|
|
105
|
+
} else {
|
|
106
|
+
console.log('[forge] First run — please set an admin password');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const readline = await import('node:readline');
|
|
110
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
111
|
+
const ask = (q) => new Promise(resolve => rl.question(q, resolve));
|
|
112
|
+
|
|
113
|
+
let pw = '';
|
|
114
|
+
while (true) {
|
|
115
|
+
pw = await ask(' Enter admin password: ');
|
|
116
|
+
if (!pw || pw.length < 4) {
|
|
117
|
+
console.log(' Password must be at least 4 characters');
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
const confirm = await ask(' Confirm password: ');
|
|
121
|
+
if (pw !== confirm) {
|
|
122
|
+
console.log(' Passwords do not match, try again');
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
rl.close();
|
|
128
|
+
|
|
129
|
+
// Encrypt and save
|
|
130
|
+
const crypto = await import('node:crypto');
|
|
131
|
+
const KEY_FILE = join(DATA_DIR, '.encrypt-key');
|
|
132
|
+
let encKey;
|
|
133
|
+
if (existsSync(KEY_FILE)) {
|
|
134
|
+
encKey = Buffer.from(readFileSync(KEY_FILE, 'utf-8').trim(), 'hex');
|
|
135
|
+
} else {
|
|
136
|
+
encKey = crypto.randomBytes(32);
|
|
137
|
+
writeFileSync(KEY_FILE, encKey.toString('hex'), { mode: 0o600 });
|
|
138
|
+
}
|
|
139
|
+
const iv = crypto.randomBytes(12);
|
|
140
|
+
const cipher = crypto.createCipheriv('aes-256-gcm', encKey, iv);
|
|
141
|
+
const encrypted = Buffer.concat([cipher.update(pw, 'utf-8'), cipher.final()]);
|
|
142
|
+
const tag = cipher.getAuthTag();
|
|
143
|
+
settings.telegramTunnelPassword = `enc:${iv.toString('base64')}.${tag.toString('base64')}.${encrypted.toString('base64')}`;
|
|
144
|
+
if (!existsSync(dirname(settingsFile))) mkdirSync(dirname(settingsFile), { recursive: true });
|
|
145
|
+
writeFileSync(settingsFile, YAML.stringify(settings), 'utf-8');
|
|
146
|
+
console.log('[forge] Admin password saved');
|
|
147
|
+
|
|
148
|
+
if (resetPassword && !isDev && !isBackground && !isRestart) {
|
|
149
|
+
process.exit(0);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
92
154
|
// ── Reset terminal server (kill port + tmux sessions) ──
|
|
93
155
|
if (resetTerminal) {
|
|
94
156
|
console.log(`[forge] Resetting terminal server (port ${terminalPort})...`);
|
|
@@ -795,6 +795,9 @@ export default function SettingsModal({ onClose }: { onClose: () => void }) {
|
|
|
795
795
|
isSet={!!secretStatus.telegramTunnelPassword}
|
|
796
796
|
onEdit={() => setEditingSecret({ field: 'telegramTunnelPassword', label: 'Admin Password' })}
|
|
797
797
|
/>
|
|
798
|
+
<p className="text-[9px] text-[var(--text-secondary)]">
|
|
799
|
+
Forgot? Run: <code className="text-[var(--accent)]">forge server start --reset-password</code>
|
|
800
|
+
</p>
|
|
798
801
|
</div>
|
|
799
802
|
|
|
800
803
|
{/* Actions */}
|
package/package.json
CHANGED