' + escapeHtml(s.headline) + '
' + escapeHtml(s.description || '') + '
diff --git a/api/auth.php b/api/auth.php old mode 100755 new mode 100644 index 48b181b..046f280 --- a/api/auth.php +++ b/api/auth.php @@ -220,4 +220,32 @@ function handle_me(): void { function handle_sso_callback(): void { respond(501, ['error' => 'SSO not configured on this instance. Configure SSO in api/auth.php']); +} + +function handle_password_change(): void { + $user = current_user(); + if (!$user) { + respond(401, ['error' => 'You must be logged in to change your password']); + return; + } + + $data = json_decode(file_get_contents('php://input'), true) ?: []; + $current_password = $data['current_password'] ?? ''; + $new_password = $data['new_password'] ?? ''; + + if (strlen($new_password) < 8 || strlen($new_password) > 72) { + respond(400, ['error' => 'New password must be between 8 and 72 characters']); + return; + } + + // Verify current password + $row = db_one("SELECT password_hash FROM users WHERE id = ?", [$user['id']]); + if (!$row || !password_verify($current_password, $row['password_hash'] ?? '')) { + respond(401, ['error' => 'Current password is incorrect']); + return; + } + + // Update password + db_update('users', ['password_hash' => password_hash($new_password, PASSWORD_DEFAULT)], 'id = ?', [$user['id']]); + respond(200, ['ok' => true]); } \ No newline at end of file diff --git a/api/index.php b/api/index.php index de49fec..db86200 100644 --- a/api/index.php +++ b/api/index.php @@ -167,6 +167,7 @@ if ($parts[0] === 'auth') { elseif ($action === 'logout' && $method === 'POST') handle_logout(); elseif ($action === 'me' && $method === 'GET') handle_me(); elseif ($action === 'sso' && $method === 'POST') handle_sso_callback(); + elseif ($action === 'password' && $method === 'POST') handle_password_change(); else respond(404, ['error' => 'Unknown auth endpoint']); } diff --git a/frontend/app.js b/frontend/app.js index 1799f4a..21955e6 100644 --- a/frontend/app.js +++ b/frontend/app.js @@ -300,12 +300,12 @@ function renderProtocolGrid(protocols) { grid.innerHTML = protocols.map(function(p) { var sourceLabel = p.source || (p.author ? '@' + p.author : ''); var tags = (p.tags || []).map(function(t) { return '' + escapeHtml(t) + ''; }).join(''); - return '
' + escapeHtml(p.description || '') + '
' + (tags ? '' + escapeHtml(s.description || '') + '
Protocol not found
' + escapeHtml(col.description||'') + '
' + escapeHtml(col.description||'') + '
A protocol is a pattern of interaction among agents, represented here in the form of a particular sequence of actions. Protocols are micro-practices — more specific than patterns and more interaction-focused than rules. A rule says "decisions are made by consensus." A protocol says "here's the step-by-step process for reaching consensus in a meeting."
+A protocol is a pattern of interaction among people, generally in the form of a particular sequence of actions. Protocols are micro-practices — more specific than patterns and more interaction-focused than rules. A rule says "decisions are made by consensus." A protocol says "here's the step-by-step process for reaching consensus in a meeting."
Protocol Droid is designed to be self-hostable and whitelabel-able. It runs on a standard LAMP stack with no external dependencies — no web fonts, no CDNs, no tracking. Deploy on any LAMP server. Configure your site name, tagline, and theme through a simple config file.
+Protocol Droid is designed to be self-hostable and whitelabel-able. It runs on a standard LAMP stack with no external dependencies — no web fonts, no CDNs, no tracking. Deploy on Cloudron, any LAMP server, or your own infrastructure. Configure your site name, tagline, and theme through a simple config file.