fixes #2355 implement API key management system

- Added API key get, creation, editing, and revocation methods.

- Updated the profile template to include API key management features.

- Updated the database schema to support the new API key system, including additional fields for key management.

- Added client-side JavaScript functionality to handle API key operations and display responses.

- Update tools/htm.ws with the new way to authenticate.

- Restriction of certain api methods when used with an api key

- Backward compatibility with older apps
This commit is contained in:
Linty
2025-06-09 20:35:57 +02:00
parent 2624be1c90
commit ae740ba3af
20 changed files with 1937 additions and 102 deletions
+17 -2
View File
@@ -84,6 +84,21 @@
<form id="methodWrapper" style="display:none;">
<div class="card">
<h3 class="card-title"><i class="icon-cog-alt"></i>Authenticate with API Key (Header)</h3>
<div class="card-content">
<p class="header-description"> Introduced in Piwigo 16, you can now use an API key in the HTTP header
to perform authenticated requests without a user session.
For more details, check out our <a href="https://github.com/Piwigo/Piwigo/wiki/Piwigo-Web-API#api-key-authentication" target="_blank">documentation</a>.
</p>
<p class="header-warning">Doesn't work when you use "INVOKE (new window)"</p>
<div class="header-setting">
<p>Authorization:</p>
<input type="text" id="apiKey" placeholder="pkid-xxxxxxxx-xxxxxxxxxxxxxxxxxxxx:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" />
</div>
</div>
</div> <!-- methodHeader -->
<div class="card" id="methodDescription" style="display:none;">
<h3 class="card-title"><i class="icon-book"></i>Description</h3>
<blockquote>
@@ -134,7 +149,7 @@
<div class="select">
<select id="responseFormat">
<option value="json" selected>JSON</option>
<option value="rest" selected>REST (xml)</option>
<option value="rest">REST (xml)</option>
<option value="php">PHP serial</option>
<option value="xmlrpc">XML RPC</option>
</select>
@@ -187,7 +202,7 @@
</div> <!-- the_container -->
<div id="the_footer">
Copyright &copy; 2002-2021 <a href="http://piwigo.org">Piwigo Team</a>
Copyright &copy; 2002-2025 <a href="http://piwigo.org">Piwigo Team</a>
</div> <!-- the_footer -->
</div>
+23
View File
@@ -349,6 +349,29 @@ input[type="text"] {
position: relative;
}
.header-setting {
display: flex;
align-items: center;
gap: 10px;
}
.header-setting input {
max-width: 580px;
width: 100%;
}
.header-description {
margin: 0;
margin-bottom: 10px;
}
.header-warning {
margin: 0;
color: red;
font-size: 12px;
font-style: italic;
}
/* #requestResultDisplay {
background: white;
} */
+75 -28
View File
@@ -93,7 +93,8 @@ $(() => {
url: ws_url,
data: { format: "json", method: "reflection.getMethodList" }
}).done(function (result) {
console.log(result);
// for debug
//console.log(result);
result = parsePwgJSON(result);
if (result != null) {
@@ -328,6 +329,8 @@ $(() => {
// invoke method
function invokeMethod(methodName, newWindow) {
$('#json-viewer').jsonViewer({});
$('#requestURLDisplay').show();
$('#requestResultDisplay').show();
@@ -335,6 +338,18 @@ $(() => {
let reqUrl = ws_url + "?format=" + $("#responseFormat").val();
const isJson = $("#responseFormat").val() === 'json';
const authorization = $('#apiKey').val();
const useCookie = '' === authorization;
let fetchOption = {};
if (!useCookie) {
fetchOption.credentials = 'omit';
fetchOption.headers = {
Authorization: authorization
}
}
// GET
if ($("#requestFormat").val() == 'get') {
reqUrl += "&method=" + methodName;
@@ -361,19 +376,11 @@ $(() => {
window.open(reqUrl);
}
else {
if ($("#responseFormat").val() === 'json') {
$("#invokeFrame").hide();
$('#json-viewer').show();
fetch(reqUrl)
.then(data => data.json())
.then(json => {
$('#json-viewer').jsonViewer(json);
})
} else {
$("#invokeFrame").show();
$('#json-viewer').hide();
$("#invokeFrame").attr('src', reqUrl);
}
fetch(reqUrl, fetchOption)
.then(data => data.text())
.then(data => {
showResponseData(data);
})
}
$('#requestURLDisplay').find('.url').html(reqUrl).end()
@@ -411,22 +418,30 @@ $(() => {
}
}
if (!newWindow && $("#responseFormat").val() === 'json') {
if (!newWindow) {
$("#invokeFrame").hide();
$('#json-viewer').show();
jQuery.ajax({
url: reqUrl,
type: 'POST',
dataType: 'json',
data: {
"method": methodName,
...params
},
success : function(data) {
$('#json-viewer').jsonViewer(data);
}
const formData = new URLSearchParams();
formData.append('method', methodName);
for (const key in params) {
formData.append(key, params[key]);
}
fetchOption.headers ??= {};
fetchOption.headers['Content-Type'] = 'application/x-www-form-urlencoded';
fetch(reqUrl, {
...fetchOption,
method: 'POST',
body: formData
})
.then(data => {
return data.text();
})
.then(data => {
showResponseData(data);
});
} else {
$("#invokeFrame").show();
$('#json-viewer').hide();
@@ -490,4 +505,36 @@ $(() => {
$('#the_body').addClass('dark-mode');
}
})
})
})
function showResponseData(data) {
const isJson = $("#responseFormat").val() === 'json';
if (isJson) {
try {
const json = JSON.parse(data);
$('#json-viewer').jsonViewer(json);
$("#invokeFrame").hide();
$('#json-viewer').show();
} catch (error) {
const iframe = $('#invokeFrame');
const iframeDoc = iframe[0].contentDocument || iframe[0].contentWindow.document;
iframeDoc.open();
iframeDoc.write(`<pre>${data}</pre>`);
iframeDoc.close();
$("#invokeFrame").show();
$('#json-viewer').hide();
}
return;
}
const iframe = $('#invokeFrame');
const iframeDoc = iframe[0].contentDocument || iframe[0].contentWindow.document;
iframeDoc.open();
iframeDoc.write(data);
iframeDoc.close();
$("#invokeFrame").show();
$('#json-viewer').hide();
}