WebOrbiton
v3.0.0.2

FlatlyPage

531 lines · 18.8 KB
  1. <?php
  2. require_once '../config.php';
  3. require_login();
  4. ​
  5. session_start();
  6. ​
  7. $message = '';
  8. $message_type = '';
  9. ​
  10. if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['create_account'])) {
  11. $username = trim($_POST['username']);
  12. $password = $_POST['password'];
  13. $confirm_password = $_POST['confirm_password'];
  14. if (empty($username) || empty($password)) {
  15. $message = 'Please enter a username and password.';
  16. $message_type = 'error';
  17. } elseif (strlen($username) < 3) {
  18. $message = 'The username needs at least 3 characters.';
  19. $message_type = 'error';
  20. } elseif (strlen($password) < 8) {
  21. $message = 'The password needs at least 8 characters.';
  22. $message_type = 'error';
  23. } elseif ($password !== $confirm_password) {
  24. $message = "The passwords don't match.";
  25. $message_type = 'error';
  26. } elseif (!preg_match('/^[a-zA-Z0-9_-]+$/', $username)) {
  27. $message = 'Use only letters, numbers, underscores and hyphens in the username.';
  28. $message_type = 'error';
  29. } else {
  30. $accounts = file_exists('hash.php') ? include('hash.php') : [];
  31. if (isset($accounts[$username])) {
  32. $message = 'That username is already taken.';
  33. $message_type = 'error';
  34. } else {
  35. ​
  36. $accounts[$username] = [
  37. 'username' => $username,
  38. 'password_hash' => password_hash($password, PASSWORD_DEFAULT),
  39. 'created_at' => date('Y-m-d H:i:s')
  40. ];
  41. $php_content = "<?php\n// hash.php - Hashed Admin Credentials\n// DO NOT EDIT MANUALLY - Use create-acc.php to manage accounts\n\nreturn " . var_export($accounts, true) . ";\n";
  42. if (file_put_contents('hash.php', $php_content)) {
  43. $message = 'Account created. You can now sign in to the newsletter manager.';
  44. $message_type = 'success';
  45. $_POST = [];
  46. } else {
  47. $message = "Couldn't save the account. Check the folder permissions on your server.";
  48. $message_type = 'error';
  49. }
  50. }
  51. }
  52. }
  53. ​
  54. if (isset($_GET['delete']) && file_exists('hash.php')) {
  55. $accounts = include('hash.php');
  56. $username_to_delete = $_GET['delete'];
  57. if (isset($accounts[$username_to_delete])) {
  58. unset($accounts[$username_to_delete]);
  59. if (empty($accounts)) {
  60. if (unlink('hash.php')) {
  61. $message = 'Account deleted. There are no newsletter accounts left.';
  62. $message_type = 'success';
  63. }
  64. } else {
  65. $php_content = "<?php\n// hash.php - Hashed Admin Credentials\n// DO NOT EDIT MANUALLY - Use create-acc.php to manage accounts\n\nreturn " . var_export($accounts, true) . ";\n";
  66. if (file_put_contents('hash.php', $php_content)) {
  67. $message = 'Account deleted.';
  68. $message_type = 'success';
  69. }
  70. }
  71. }
  72. }
  73. ​
  74. $existing_accounts = file_exists('hash.php') ? include('hash.php') : [];
  75. ?>
  76. <!DOCTYPE html>
  77. <html lang="en">
  78. <head>
  79. <meta charset="UTF-8">
  80. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  81. <title>Account Creator - Newsletter Manager</title>
  82. <link rel="icon" href="nl.ico?v=<?= filemtime(__DIR__ . '/newsletter/nl.ico') ?>" type="image/x-icon">
  83. <style>
  84. * { margin: 0; padding: 0; box-sizing: border-box; }
  85. :root {
  86. --bg: #0a0a0a;
  87. --bg-elevated: #111;
  88. --bg-card: #161616;
  89. --bg-hover: #1c1c1c;
  90. --border: #262626;
  91. --border-focus: #404040;
  92. --text: #fafafa;
  93. --text-muted: #737373;
  94. --text-subtle: #525252;
  95. --accent: #fff;
  96. --success: #22c55e;
  97. --error: #ef4444;
  98. --warning: #eab308;
  99. --radius: 12px;
  100. --radius-sm: 8px;
  101. }
  102. body {
  103. font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  104. background: var(--bg);
  105. color: var(--text);
  106. padding: 20px;
  107. }
  108. .container {
  109. max-width: 800px;
  110. margin: 0 auto;
  111. }
  112. .header {
  113. background: var(--bg-card);
  114. border: 1px solid var(--border);
  115. padding: 30px;
  116. border-radius: var(--radius);
  117. margin-bottom: 30px;
  118. text-align: center;
  119. }
  120. .header-icon {
  121. width: 48px;
  122. height: 48px;
  123. margin: 0 auto 15px;
  124. color: var(--accent);
  125. }
  126. .header h1 {
  127. font-size: 28px;
  128. margin-bottom: 10px;
  129. }
  130. .header p {
  131. color: var(--text-muted);
  132. font-size: 14px;
  133. }
  134. .card {
  135. background: var(--bg-card);
  136. border: 1px solid var(--border);
  137. border-radius: var(--radius);
  138. padding: 30px;
  139. margin-bottom: 30px;
  140. }
  141. .card h2 {
  142. margin-bottom: 20px;
  143. font-size: 20px;
  144. display: flex;
  145. align-items: center;
  146. gap: 10px;
  147. }
  148. .card h2 svg {
  149. width: 24px;
  150. height: 24px;
  151. }
  152. .alert {
  153. padding: 15px 20px;
  154. border-radius: var(--radius-sm);
  155. margin-bottom: 20px;
  156. font-size: 14px;
  157. border: 1px solid;
  158. display: flex;
  159. align-items: center;
  160. gap: 12px;
  161. }
  162. .alert svg {
  163. width: 20px;
  164. height: 20px;
  165. flex-shrink: 0;
  166. }
  167. .alert-success {
  168. background: rgba(34, 197, 94, 0.1);
  169. color: var(--success);
  170. border-color: rgba(34, 197, 94, 0.2);
  171. }
  172. .alert-error {
  173. background: rgba(239, 68, 68, 0.1);
  174. color: var(--error);
  175. border-color: rgba(239, 68, 68, 0.2);
  176. }
  177. .alert-warning {
  178. background: rgba(234, 179, 8, 0.1);
  179. color: var(--warning);
  180. border-color: rgba(234, 179, 8, 0.2);
  181. }
  182. .alert-info {
  183. background: rgba(59, 130, 246, 0.1);
  184. color: #3b82f6;
  185. border-color: rgba(59, 130, 246, 0.2);
  186. }
  187. .form-group {
  188. margin-bottom: 20px;
  189. }
  190. .form-group label {
  191. display: block;
  192. margin-bottom: 8px;
  193. color: var(--text);
  194. font-weight: 500;
  195. font-size: 14px;
  196. }
  197. .form-group input {
  198. width: 100%;
  199. padding: 12px;
  200. border: 1px solid var(--border);
  201. background: var(--bg-elevated);
  202. color: var(--text);
  203. border-radius: var(--radius-sm);
  204. font-size: 14px;
  205. transition: border-color 0.3s;
  206. }
  207. .form-group input:focus {
  208. outline: none;
  209. border-color: var(--border-focus);
  210. }
  211. .form-help {
  212. font-size: 12px;
  213. color: var(--text-muted);
  214. margin-top: 5px;
  215. }
  216. .btn {
  217. padding: 12px 24px;
  218. border: none;
  219. border-radius: var(--radius-sm);
  220. font-size: 14px;
  221. font-weight: 600;
  222. cursor: pointer;
  223. text-decoration: none;
  224. display: inline-flex;
  225. align-items: center;
  226. gap: 8px;
  227. transition: opacity 0.2s;
  228. }
  229. .btn:hover { opacity: 0.9; }
  230. .btn svg {
  231. width: 16px;
  232. height: 16px;
  233. }
  234. .btn-primary {
  235. background: var(--accent);
  236. color: var(--bg);
  237. }
  238. .btn-danger {
  239. background: var(--error);
  240. color: white;
  241. }
  242. .btn-sm {
  243. padding: 8px 16px;
  244. font-size: 12px;
  245. }
  246. table {
  247. width: 100%;
  248. border-collapse: collapse;
  249. }
  250. table th,
  251. table td {
  252. padding: 12px;
  253. text-align: left;
  254. border-bottom: 1px solid var(--border);
  255. }
  256. table th {
  257. background: var(--bg-elevated);
  258. font-weight: 600;
  259. font-size: 14px;
  260. color: var(--text);
  261. }
  262. table td {
  263. font-size: 14px;
  264. color: var(--text-muted);
  265. }
  266. .empty-state {
  267. text-align: center;
  268. padding: 40px 20px;
  269. color: var(--text-muted);
  270. }
  271. .empty-state svg {
  272. width: 64px;
  273. height: 64px;
  274. margin: 0 auto 20px;
  275. opacity: 0.3;
  276. }
  277. code {
  278. background: var(--bg-elevated);
  279. padding: 2px 6px;
  280. border-radius: 4px;
  281. font-family: 'Courier New', monospace;
  282. font-size: 13px;
  283. color: var(--text-muted);
  284. }
  285. .password-strength {
  286. height: 4px;
  287. background: var(--border);
  288. border-radius: 2px;
  289. margin-top: 8px;
  290. overflow: hidden;
  291. }
  292. .password-strength-bar {
  293. height: 100%;
  294. width: 0;
  295. transition: width 0.3s, background-color 0.3s;
  296. }
  297. .strength-weak { background: var(--error); width: 33%; }
  298. .strength-medium { background: var(--warning); width: 66%; }
  299. .strength-strong { background: var(--success); width: 100%; }
  300. </style>
  301. </head>
  302. <body>
  303. <div class="container">
  304. <div class="header">
  305. <svg class="header-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  306. <path d="M16 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path>
  307. <circle cx="8.5" cy="7" r="4"></circle>
  308. <line x1="20" y1="8" x2="20" y2="14"></line>
  309. <line x1="23" y1="11" x2="17" y2="11"></line>
  310. </svg>
  311. <h1>Account Creator</h1>
  312. <p>Add or remove the accounts that can use the newsletter manager</p>
  313. </div>
  314. <?php if ($message): ?>
  315. <div class="alert alert-<?= $message_type ?>">
  316. <?php if ($message_type === 'success'): ?>
  317. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  318. <path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path>
  319. <polyline points="22 4 12 14.01 9 11.01"></polyline>
  320. </svg>
  321. <?php elseif ($message_type === 'error'): ?>
  322. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  323. <circle cx="12" cy="12" r="10"></circle>
  324. <line x1="15" y1="9" x2="9" y2="15"></line>
  325. <line x1="9" y1="9" x2="15" y2="15"></line>
  326. </svg>
  327. <?php else: ?>
  328. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  329. <path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"></path>
  330. <line x1="12" y1="9" x2="12" y2="13"></line>
  331. <line x1="12" y1="17" x2="12.01" y2="17"></line>
  332. </svg>
  333. <?php endif; ?>
  334. <span><?= htmlspecialchars($message) ?></span>
  335. </div>
  336. <?php endif; ?>
  337. <div class="card">
  338. <h2>
  339. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  340. <path d="M16 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path>
  341. <circle cx="8.5" cy="7" r="4"></circle>
  342. <polyline points="17 11 19 13 23 9"></polyline>
  343. </svg>
  344. Create New Account
  345. </h2>
  346. <form method="POST" id="accountForm">
  347. <div class="form-group">
  348. <label>Username</label>
  349. <input type="text" name="username" id="username" required minlength="3" pattern="[a-zA-Z0-9_-]+" value="<?= htmlspecialchars($_POST['username'] ?? '') ?>">
  350. <div class="form-help">Minimum 3 characters. Only letters, numbers, underscore and dash allowed.</div>
  351. </div>
  352. <div class="form-group">
  353. <label>Password</label>
  354. <input type="password" name="password" id="password" required minlength="8">
  355. <div class="password-strength">
  356. <div class="password-strength-bar" id="strengthBar"></div>
  357. </div>
  358. <div class="form-help">Minimum 8 characters. Use a strong password with letters, numbers and symbols.</div>
  359. </div>
  360. <div class="form-group">
  361. <label>Confirm Password</label>
  362. <input type="password" name="confirm_password" id="confirm_password" required minlength="8">
  363. </div>
  364. <button type="submit" name="create_account" class="btn btn-primary">
  365. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  366. <line x1="12" y1="5" x2="12" y2="19"></line>
  367. <line x1="5" y1="12" x2="19" y2="12"></line>
  368. </svg>
  369. Create Account
  370. </button>
  371. </form>
  372. </div>
  373. <?php if (!empty($existing_accounts)): ?>
  374. <div class="card">
  375. <h2>
  376. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  377. <path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path>
  378. <circle cx="9" cy="7" r="4"></circle>
  379. <path d="M23 21v-2a4 4 0 0 0-3-3.87"></path>
  380. <path d="M16 3.13a4 4 0 0 1 0 7.75"></path>
  381. </svg>
  382. Existing Accounts (<?= count($existing_accounts) ?>)
  383. </h2>
  384. <table>
  385. <thead>
  386. <tr>
  387. <th>Username</th>
  388. <th>Created</th>
  389. <th>Actions</th>
  390. </tr>
  391. </thead>
  392. <tbody>
  393. <?php foreach ($existing_accounts as $username => $account): ?>
  394. <tr>
  395. <td><strong><?= htmlspecialchars($username) ?></strong></td>
  396. <td><?= htmlspecialchars($account['created_at']) ?></td>
  397. <td>
  398. <a href="?delete=<?= urlencode($username) ?>" class="btn btn-danger btn-sm" onclick="return confirm('Delete account \'<?= htmlspecialchars($username) ?>\'?\n\nYou can\'t undo this.')">
  399. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  400. <polyline points="3 6 5 6 21 6"></polyline>
  401. <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
  402. </svg>
  403. Delete
  404. </a>
  405. </td>
  406. </tr>
  407. <?php endforeach; ?>
  408. </tbody>
  409. </table>
  410. </div>
  411. <?php else: ?>
  412. <div class="card">
  413. <div class="empty-state">
  414. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  415. <path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path>
  416. <circle cx="9" cy="7" r="4"></circle>
  417. <path d="M23 21v-2a4 4 0 0 0-3-3.87"></path>
  418. <path d="M16 3.13a4 4 0 0 1 0 7.75"></path>
  419. </svg>
  420. <p>No accounts yet.<br>Add your first one above.</p>
  421. </div>
  422. </div>
  423. <?php endif; ?>
  424. <div class="card">
  425. <h2>
  426. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  427. <circle cx="12" cy="12" r="10"></circle>
  428. <line x1="12" y1="16" x2="12" y2="12"></line>
  429. <line x1="12" y1="8" x2="12.01" y2="8"></line>
  430. </svg>
  431. Important Information
  432. </h2>
  433. <ul style="line-height: 2; color: var(--text-muted); margin-left: 20px;">
  434. <li>Passwords are hashed using bcrypt (industry standard)</li>
  435. <li>Each account gets stored in <code>hash.php</code></li>
  436. <li>Use these credentials to login at <code>manager.php</code></li>
  437. <li>You can create multiple accounts before accessing the manager</li>
  438. </ul>
  439. </div>
  440. </div>
  441. <script>
  442. const passwordInput = document.getElementById('password');
  443. const strengthBar = document.getElementById('strengthBar');
  444. passwordInput.addEventListener('input', function() {
  445. const password = this.value;
  446. const length = password.length;
  447. strengthBar.className = 'password-strength-bar';
  448. if (length === 0) {
  449. strengthBar.style.width = '0';
  450. } else if (length < 8) {
  451. strengthBar.classList.add('strength-weak');
  452. } else if (length < 12 || !/[0-9]/.test(password) || !/[a-zA-Z]/.test(password)) {
  453. strengthBar.classList.add('strength-medium');
  454. } else {
  455. strengthBar.classList.add('strength-strong');
  456. }
  457. });
  458. document.getElementById('accountForm').addEventListener('submit', function(e) {
  459. const password = document.getElementById('password').value;
  460. const confirm = document.getElementById('confirm_password').value;
  461. if (password !== confirm) {
  462. e.preventDefault();
  463. alert('Passwords do not match!');
  464. return false;
  465. }
  466. });
  467. </script>
  468. </body>
  469. </html>