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:
parent
2624be1c90
commit
ae740ba3af
@ -74,6 +74,8 @@ if (isset($_POST['login']))
|
||||
// {redirect (final) = http://localhost/piwigo/git/admin.php}
|
||||
$root_url = get_absolute_root_url();
|
||||
|
||||
$_SESSION['connected_with'] = 'pwg_ui';
|
||||
|
||||
redirect(
|
||||
empty($redirect_to)
|
||||
? get_gallery_home_url()
|
||||
|
@ -460,6 +460,34 @@ $conf['session_use_ip_address'] = true;
|
||||
// session").
|
||||
$conf['session_gc_probability'] = 1;
|
||||
|
||||
// +-----------------------------------------------------------------------+
|
||||
// | api key |
|
||||
// +-----------------------------------------------------------------------+
|
||||
|
||||
// api_key_duration: available duration options (in days) for API key creation.
|
||||
// Array of predefined durations that will be displayed in the select dropdown
|
||||
// when creating a new API key. Use 'custom' to allow users to set a specific
|
||||
// expiration date with a date picker input.
|
||||
$conf['api_key_duration'] = ['30', '90', '180', '365', 'custom'];
|
||||
|
||||
// The following API methods are prohibited when making requests with an API key.
|
||||
// These restrictions are in place for security reasons and to prevent unauthorized
|
||||
// access to sensitive operations that require higher-level authentication.
|
||||
$conf['api_key_forbidden_methods'] = array(
|
||||
// users
|
||||
'pwg.users.generatePasswordLink',
|
||||
'pwg.users.getAuthKey',
|
||||
'pwg.users.setMainUser',
|
||||
'pwg.users.setInfo',
|
||||
// plugins
|
||||
'pwg.plugins.performAction',
|
||||
// themes
|
||||
'pwg.themes.performAction',
|
||||
// extensions
|
||||
'pwg.extensions.ignoreUpdate',
|
||||
'pwg.extensions.update',
|
||||
);
|
||||
|
||||
// +-----------------------------------------------------------------------+
|
||||
// | debug/performance |
|
||||
// +-----------------------------------------------------------------------+
|
||||
|
@ -153,6 +153,13 @@ SELECT data
|
||||
*/
|
||||
function pwg_session_write($session_id, $data)
|
||||
{
|
||||
// when the request is authenticated via api_key (PWG_API_KEY_REQUEST),
|
||||
// you do not want the session to be written to the database (no user session persistence)
|
||||
// this avoids polluting the session table with stateless API accesses
|
||||
if (defined('PWG_API_KEY_REQUEST'))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
$query = '
|
||||
REPLACE INTO '.SESSIONS_TABLE.'
|
||||
(id,data,expiration)
|
||||
|
@ -1661,14 +1661,28 @@ function get_recent_photos_sql($db_field)
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
function auth_key_login($auth_key)
|
||||
function auth_key_login($auth_key, $connection_by_header=false)
|
||||
{
|
||||
global $conf, $user, $page;
|
||||
|
||||
if (!preg_match('/^[a-z0-9]{30}$/i', $auth_key))
|
||||
$valid_key = false;
|
||||
$secret_key = null;
|
||||
if (preg_match('/^[a-z0-9]{30}$/i', $auth_key))
|
||||
{
|
||||
return false;
|
||||
$valid_key = 'auth_key';
|
||||
}
|
||||
else if (
|
||||
preg_match('/^pkid-\d{8}-[a-z0-9]{20}:[a-z0-9]{40}$/i', $auth_key)
|
||||
and $connection_by_header
|
||||
)
|
||||
{
|
||||
$valid_key = 'api_key';
|
||||
$tmp_key = explode(':', $auth_key);
|
||||
$auth_key = $tmp_key[0];
|
||||
$secret_key = $tmp_key[1];
|
||||
}
|
||||
|
||||
if (!$valid_key) return false;
|
||||
|
||||
$query = '
|
||||
SELECT
|
||||
@ -1689,6 +1703,22 @@ SELECT
|
||||
|
||||
$key = $keys[0];
|
||||
|
||||
// the key is an api_key
|
||||
if ('api_key' === $valid_key)
|
||||
{
|
||||
// check secret
|
||||
if (!pwg_password_verify($secret_key, $key['apikey_secret']))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// is the key is revoked?
|
||||
if (null != $key['revoked_on'])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// is the key still valid?
|
||||
if (strtotime($key['expired_on']) < strtotime($key['dbnow']))
|
||||
{
|
||||
@ -1697,12 +1727,34 @@ SELECT
|
||||
}
|
||||
|
||||
// admin/webmaster/guest can't get connected with authentication keys
|
||||
if (!in_array($key['status'], array('normal','generic')))
|
||||
if ('auth_key' === $valid_key and !in_array($key['status'], array('normal','generic')))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
$user['id'] = $key['user_id'];
|
||||
|
||||
// update last used key
|
||||
single_update(
|
||||
USER_AUTH_KEYS_TABLE,
|
||||
array('last_used_on' => $key['dbnow']),
|
||||
array(
|
||||
'user_id' => $user['id'],
|
||||
'auth_key' => $key['auth_key']
|
||||
),
|
||||
);
|
||||
|
||||
// set the type of connection
|
||||
$_SESSION['connected_with'] = $valid_key;
|
||||
|
||||
// if the connection is made via an API key in the header,
|
||||
// access is authenticated without creating a persistent user session
|
||||
// this enables stateless authentication for API calls
|
||||
if ($connection_by_header)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
log_user($user['id'], false);
|
||||
trigger_notify('login_success', $key['username']);
|
||||
|
||||
@ -1771,6 +1823,7 @@ SELECT
|
||||
'created_on' => $now,
|
||||
'duration' => $conf['auth_key_duration'],
|
||||
'expired_on' => $expiration,
|
||||
'key_type' => 'auth_key',
|
||||
);
|
||||
|
||||
single_insert(USER_AUTH_KEYS_TABLE, $key);
|
||||
@ -1799,6 +1852,7 @@ UPDATE '.USER_AUTH_KEYS_TABLE.'
|
||||
SET expired_on = NOW()
|
||||
WHERE user_id = '.$user_id.'
|
||||
AND expired_on > NOW()
|
||||
AND key_type = \'auth_key\'
|
||||
;';
|
||||
pwg_query($query);
|
||||
}
|
||||
@ -2383,4 +2437,200 @@ SELECT
|
||||
'account' => $updates
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new api_key
|
||||
*
|
||||
* @since 16
|
||||
* @param int $user_id
|
||||
* @param int|null $duration
|
||||
* @param string $key_name
|
||||
* @return array auth_key / apikey_secret / apikey_name /
|
||||
* user_id / created_on / duration / expired_on / key_type
|
||||
*/
|
||||
function create_api_key($user_id, $duration, $key_name)
|
||||
{
|
||||
$key_id = 'pkid-'.date('Ymd').'-'.generate_key(20);
|
||||
$key_secret = generate_key(40);
|
||||
|
||||
list($dbnow) = pwg_db_fetch_row(pwg_query('SELECT NOW();'));
|
||||
|
||||
$key = array(
|
||||
'auth_key' => $key_id,
|
||||
'apikey_secret' => pwg_password_hash($key_secret),
|
||||
'apikey_name' => $key_name,
|
||||
'user_id' => $user_id,
|
||||
'created_on' => $dbnow,
|
||||
'key_type' => 'api_key'
|
||||
);
|
||||
|
||||
if (!empty($duration))
|
||||
{
|
||||
$query = '
|
||||
SELECT
|
||||
ADDDATE(NOW(), INTERVAL '.($duration * 60 * 60 * 24).' SECOND)
|
||||
;';
|
||||
list($expiration) = pwg_db_fetch_row(pwg_query($query));
|
||||
$key['duration'] = $duration;
|
||||
}
|
||||
$key['expired_on'] = $expiration;
|
||||
|
||||
single_insert(USER_AUTH_KEYS_TABLE, $key);
|
||||
|
||||
$key['apikey_secret'] = $key_secret;
|
||||
return $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Revoke a api_key
|
||||
*
|
||||
* @since 16
|
||||
* @param int $user_id
|
||||
* @param string $pkid
|
||||
* @return string|bool
|
||||
*/
|
||||
function revoke_api_key($user_id, $pkid)
|
||||
{
|
||||
$query = '
|
||||
SELECT
|
||||
COUNT(*),
|
||||
NOW()
|
||||
FROM `'.USER_AUTH_KEYS_TABLE.'`
|
||||
WHERE auth_key = "'.$pkid.'"
|
||||
AND user_id = '.$user_id.'
|
||||
;';
|
||||
|
||||
list($key, $now) = pwg_db_fetch_row(pwg_query($query));
|
||||
if ($key == 0)
|
||||
{
|
||||
return l10n('API Key not found');
|
||||
}
|
||||
|
||||
single_update(
|
||||
USER_AUTH_KEYS_TABLE,
|
||||
array('revoked_on' => $now),
|
||||
array(
|
||||
'auth_key' => $pkid,
|
||||
'user_id' => $user_id
|
||||
)
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit a api_key
|
||||
*
|
||||
* @since 16
|
||||
* @param int $user_id
|
||||
* @param string $pkid
|
||||
* @return string|bool
|
||||
*/
|
||||
function edit_api_key($user_id, $pkid, $api_name)
|
||||
{
|
||||
$query = '
|
||||
SELECT
|
||||
COUNT(*)
|
||||
FROM `'.USER_AUTH_KEYS_TABLE.'`
|
||||
WHERE auth_key = "'.$pkid.'"
|
||||
AND user_id = '.$user_id.'
|
||||
;';
|
||||
|
||||
list($key) = pwg_db_fetch_row(pwg_query($query));
|
||||
if ($key == 0)
|
||||
{
|
||||
return l10n('API Key not found');
|
||||
}
|
||||
|
||||
single_update(
|
||||
USER_AUTH_KEYS_TABLE,
|
||||
array('apikey_name' => $api_name),
|
||||
array(
|
||||
'auth_key' => $pkid,
|
||||
'user_id' => $user_id
|
||||
)
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all api_key
|
||||
*
|
||||
* @since 16
|
||||
* @param string $user_id
|
||||
* @return array|false
|
||||
*/
|
||||
function get_api_key($user_id)
|
||||
{
|
||||
$query = '
|
||||
SELECT *
|
||||
FROM `'.USER_AUTH_KEYS_TABLE.'`
|
||||
WHERE user_id = '.$user_id.'
|
||||
AND key_type = "api_key"
|
||||
;';
|
||||
|
||||
$api_keys = query2array($query);
|
||||
if (!$api_keys) return false;
|
||||
|
||||
$query = '
|
||||
SELECT
|
||||
NOW()
|
||||
;';
|
||||
list($now) = pwg_db_fetch_row(pwg_query($query));
|
||||
|
||||
foreach ($api_keys as $i => $api_key)
|
||||
{
|
||||
$api_key['apikey_secret'] = str_repeat("*", 40);
|
||||
unset($api_key['auth_key_id'], $api_key['user_id'], $api_key['key_type']);
|
||||
|
||||
$api_key['created_on_format'] = format_date($api_key['created_on'], array('day', 'month', 'year'));
|
||||
$api_key['expired_on_format'] = format_date($api_key['expired_on'], array('day', 'month', 'year'));
|
||||
$api_key['last_used_on_since'] =
|
||||
$api_key['last_used_on']
|
||||
? time_since($api_key['last_used_on'], 'day')
|
||||
: l10n('Never');
|
||||
|
||||
$expired_on = str2DateTime($api_key['expired_on']);
|
||||
$now = str2DateTime($now);
|
||||
|
||||
$api_key['is_expired'] = $expired_on < $now;
|
||||
if ($api_key['is_expired'])
|
||||
{
|
||||
$api_key['expiration'] = l10n('Expired');
|
||||
}
|
||||
else
|
||||
{
|
||||
$diff = dateDiff($now, $expired_on);
|
||||
if ($diff->days > 0)
|
||||
{
|
||||
$api_key['expiration'] = l10n('%d days', $diff->days);
|
||||
}
|
||||
elseif ($diff->h > 0)
|
||||
{
|
||||
$api_key['expiration'] = l10n('%d hours', $diff->h);
|
||||
}
|
||||
else
|
||||
{
|
||||
$api_key['expiration'] = l10n('%d minutes', $diff->i);
|
||||
}
|
||||
}
|
||||
|
||||
$api_key['expired_on_since'] = time_since($api_key['expired_on'], 'day');
|
||||
|
||||
$api_key['revoked_on_since'] =
|
||||
$api_key['revoked_on']
|
||||
? time_since($api_key['revoked_on'], 'day')
|
||||
: null;
|
||||
|
||||
$api_key['revoked_on_message'] =
|
||||
$api_key['revoked_on']
|
||||
? l10n('This API key was manually revoked on %s', format_date($api_key['revoked_on'], array('day', 'month', 'year')))
|
||||
: null;
|
||||
|
||||
$api_keys[$i] = $api_key;
|
||||
}
|
||||
|
||||
return $api_keys;
|
||||
}
|
||||
?>
|
||||
|
@ -56,6 +56,44 @@ if (isset($_GET['auth']))
|
||||
auth_key_login($_GET['auth']);
|
||||
}
|
||||
|
||||
// HTTP_AUTHORIZATION api_key
|
||||
if (
|
||||
defined('IN_WS')
|
||||
and isset($_SERVER['HTTP_AUTHORIZATION'])
|
||||
and !empty($_SERVER['HTTP_AUTHORIZATION'])
|
||||
and isset($_REQUEST['method'])
|
||||
)
|
||||
{
|
||||
$auth_header = pwg_db_real_escape_string($_SERVER['HTTP_AUTHORIZATION']) ?? null;
|
||||
|
||||
if ($auth_header)
|
||||
{
|
||||
$authenticate = auth_key_login($auth_header, true);
|
||||
if (!$authenticate)
|
||||
{
|
||||
include_once(PHPWG_ROOT_PATH.'include/ws_init.inc.php');
|
||||
$service->sendResponse(new PwgError(401, 'Invalid api_key'));
|
||||
exit;
|
||||
}
|
||||
define('PWG_API_KEY_REQUEST', true);
|
||||
|
||||
// set pwg_token for api_key request
|
||||
if (isset($_POST['pwg_token']))
|
||||
{
|
||||
$_POST['pwg_token'] = get_pwg_token();
|
||||
}
|
||||
|
||||
if (isset($_GET['pwg_token']))
|
||||
{
|
||||
$_GET['pwg_token'] = get_pwg_token();
|
||||
}
|
||||
|
||||
// logger
|
||||
global $logger;
|
||||
$logger->info('[api_key][pkid='.explode(':', $auth_header)[0].'][method='.$_REQUEST['method'].']');
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
defined('IN_WS')
|
||||
and isset($_REQUEST['method'])
|
||||
@ -70,6 +108,7 @@ if (
|
||||
$service->sendResponse(new PwgError(999, 'Invalid username/password'));
|
||||
exit();
|
||||
}
|
||||
$_SESSION['connected_with'] = 'pwg.images.uploadAsync';
|
||||
}
|
||||
|
||||
$page['user_use_cache'] = true;
|
||||
|
@ -517,6 +517,11 @@ Request format: ".@$this->_requestFormat." Response format: ".@$this->_responseF
|
||||
return new PwgError(401, 'Access denied');
|
||||
}
|
||||
|
||||
if (!$this->isAuthorizedMethodForAPIKEY())
|
||||
{
|
||||
return new PwgError(401, 'Access denied');
|
||||
}
|
||||
|
||||
// parameter check and data correction
|
||||
$signature = $method['signature'];
|
||||
$missing_params = array();
|
||||
@ -679,5 +684,27 @@ Request format: ".@$this->_requestFormat." Response format: ".@$this->_responseF
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
|
||||
function isAuthorizedMethodForAPIKEY()
|
||||
{
|
||||
global $conf;
|
||||
|
||||
// if the request is made with an API key (via header or session API key),
|
||||
// we check whether the requested method is on the
|
||||
// list of prohibited methods ($conf['api_key_forbidden_methods']) for API keys
|
||||
// if it is, access is refused (false)
|
||||
if (
|
||||
defined('PWG_API_KEY_REQUEST')
|
||||
OR (isset($_SESSION['connected_with']) AND 'ws_session_login_api_key' === $_SESSION['connected_with'])
|
||||
)
|
||||
{
|
||||
if (in_array($_REQUEST['method'], $conf['api_key_forbidden_methods']))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
@ -347,8 +347,24 @@ DELETE FROM '. RATE_TABLE .'
|
||||
*/
|
||||
function ws_session_login($params, &$service)
|
||||
{
|
||||
if (try_log_user($params['username'], $params['password'], false))
|
||||
if (defined('PWG_API_KEY_REQUEST'))
|
||||
{
|
||||
return new PwgError(401, 'Cannot use this method with an api key');
|
||||
}
|
||||
|
||||
if (preg_match('/^pkid-\d{8}-[a-z0-9]{20}$/i', $params['username']))
|
||||
{
|
||||
$secret = pwg_db_real_escape_string($params['password']);
|
||||
$authenticate = auth_key_login($params['username'].':'.$secret);
|
||||
if ($authenticate)
|
||||
{
|
||||
$_SESSION['connected_with'] = 'ws_session_login_api_key';
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (try_log_user($params['username'], $params['password'], false))
|
||||
{
|
||||
$_SESSION['connected_with'] = 'ws_session_login';
|
||||
return true;
|
||||
}
|
||||
return new PwgError(999, 'Invalid username/password');
|
||||
@ -362,6 +378,11 @@ function ws_session_login($params, &$service)
|
||||
*/
|
||||
function ws_session_logout($params, &$service)
|
||||
{
|
||||
if (defined('PWG_API_KEY_REQUEST'))
|
||||
{
|
||||
return new PwgError(401, 'Cannot use this method with an api key');
|
||||
}
|
||||
|
||||
if (!is_a_guest())
|
||||
{
|
||||
logout_user();
|
||||
@ -390,11 +411,13 @@ function ws_session_getStatus($params, &$service)
|
||||
$res['current_datetime'] = $dbnow;
|
||||
$res['version'] = PHPWG_VERSION;
|
||||
$res['save_visits'] = do_log();
|
||||
$res['connected_with'] = $_SESSION['connected_with'] ?? null;
|
||||
|
||||
// Piwigo Remote Sync does not support receiving the new (version 14) output "save_visits"
|
||||
if (isset($_SERVER['HTTP_USER_AGENT']) and preg_match('/^PiwigoRemoteSync/', $_SERVER['HTTP_USER_AGENT']))
|
||||
{
|
||||
unset($res['save_visits']);
|
||||
unset($res['connected_with']);
|
||||
}
|
||||
|
||||
// Piwigo Remote Sync does not support receiving the available sizes
|
||||
@ -1151,4 +1174,5 @@ SELECT
|
||||
'summary' => $search_summary
|
||||
);
|
||||
}
|
||||
?>
|
||||
|
||||
?>
|
@ -629,6 +629,8 @@ SELECT '.$conf['user_fields']['password'].' AS password
|
||||
$params['password'] = $params['new_password'];
|
||||
}
|
||||
|
||||
|
||||
// Unset admin field also new and conf password
|
||||
unset(
|
||||
$params['new_password'],
|
||||
$params['conf_new_password'],
|
||||
@ -949,4 +951,158 @@ function ws_set_main_user($params, &$service)
|
||||
conf_update_param('webmaster_id', $params['user_id']);
|
||||
return 'The main user has been changed.';
|
||||
}
|
||||
|
||||
/**
|
||||
* API method
|
||||
* Create a new api key for the current user
|
||||
* @since 15
|
||||
* @param mixed[] $params
|
||||
*/
|
||||
function ws_create_api_key($params, &$service)
|
||||
{
|
||||
global $user, $logger;
|
||||
|
||||
if (is_a_guest() OR !can_manage_api_key()) return new PwgError(401, 'Acces Denied');
|
||||
|
||||
if (get_pwg_token() != $params['pwg_token'])
|
||||
{
|
||||
return new PwgError(403, 'Invalid security token');
|
||||
}
|
||||
|
||||
if ($params['duration'] < 1 OR $params['duration'] > 999999)
|
||||
{
|
||||
return new PwgError(400, 'Invalid duration max days is 999999');
|
||||
}
|
||||
|
||||
if (strlen($params['key_name']) > 100)
|
||||
{
|
||||
return new PwgError(400, 'Key name is too long');
|
||||
}
|
||||
|
||||
$key_name = pwg_db_real_escape_string($params['key_name']);
|
||||
$duration = 0 == $params['duration'] ? 1 : $params['duration'];
|
||||
|
||||
$secret = create_api_key($user['id'], $duration, $key_name);
|
||||
|
||||
$logger->info('[api_key][user_id='.$user['id'].'][action=create][key_name='.$params['key_name'].']');
|
||||
|
||||
return $secret;
|
||||
}
|
||||
|
||||
/**
|
||||
* API method
|
||||
* Revoke a api key for the current user
|
||||
* @since 15
|
||||
* @param mixed[] $params
|
||||
*/
|
||||
function ws_revoke_api_key($params, &$service)
|
||||
{
|
||||
global $user, $logger;
|
||||
|
||||
if (is_a_guest() OR !can_manage_api_key()) return new PwgError(401, 'Acces Denied');
|
||||
|
||||
if (get_pwg_token() != $params['pwg_token'])
|
||||
{
|
||||
return new PwgError(403, l10n('Invalid security token'));
|
||||
}
|
||||
|
||||
if (!preg_match('/^pkid-\d{8}-[a-z0-9]{20}$/i', $params['pkid']))
|
||||
{
|
||||
return new PwgError(403, l10n('Invalid pkid format'));
|
||||
}
|
||||
|
||||
$revoked_key = revoke_api_key($user['id'], $params['pkid']);
|
||||
|
||||
if (true !== $revoked_key)
|
||||
{
|
||||
return new PwgError(403, $revoked_key);
|
||||
}
|
||||
|
||||
$logger->info('[api_key][user_id='.$user['id'].'][action=revoke][pkid='.$params['pkid'].']');
|
||||
|
||||
return l10n('API Key has been successfully revoked.');
|
||||
}
|
||||
|
||||
/**
|
||||
* API method
|
||||
* Edit a api key for the current user
|
||||
* @since 15
|
||||
* @param mixed[] $params
|
||||
*/
|
||||
function ws_edit_api_key($params, &$service)
|
||||
{
|
||||
global $user, $logger;
|
||||
|
||||
if (is_a_guest())
|
||||
{
|
||||
return new PwgError(401, 'Acces Denied');
|
||||
}
|
||||
|
||||
if (!can_manage_api_key())
|
||||
{
|
||||
return new PwgError(401, 'Acces Denied');
|
||||
}
|
||||
|
||||
if (get_pwg_token() != $params['pwg_token'])
|
||||
{
|
||||
return new PwgError(403, l10n('Invalid security token'));
|
||||
}
|
||||
|
||||
if (!preg_match('/^pkid-\d{8}-[a-z0-9]{20}$/i', $params['pkid']))
|
||||
{
|
||||
return new PwgError(403, l10n('Invalid pkid format'));
|
||||
}
|
||||
|
||||
$key_name = pwg_db_real_escape_string($params['key_name']);
|
||||
$edited_key = edit_api_key($user['id'], $params['pkid'], $key_name);
|
||||
|
||||
if (true !== $edited_key)
|
||||
{
|
||||
return new PwgError(403, $edited_key);
|
||||
}
|
||||
|
||||
$logger->info('[api_key][user_id='.$user['id'].'][action=edit][pkid='.$params['pkid'].'][new_name='.$key_name.']');
|
||||
|
||||
return l10n('API Key has been successfully edited.');
|
||||
}
|
||||
|
||||
/**
|
||||
* API method
|
||||
* Get all api key for the current user
|
||||
* @since 15
|
||||
* @param mixed[] $params
|
||||
*/
|
||||
function ws_get_api_key($params, &$service)
|
||||
{
|
||||
global $user;
|
||||
|
||||
if (is_a_guest())
|
||||
{
|
||||
return new PwgError(401, 'Acces Denied');
|
||||
}
|
||||
|
||||
if (!can_manage_api_key())
|
||||
{
|
||||
return new PwgError(401, 'Acces Denied');
|
||||
}
|
||||
|
||||
if (get_pwg_token() != $params['pwg_token'])
|
||||
{
|
||||
return new PwgError(403, 'Invalid security token');
|
||||
}
|
||||
|
||||
$api_keys = get_api_key($user['id']);
|
||||
|
||||
return $api_keys ?? l10n('No API key found');
|
||||
}
|
||||
|
||||
function can_manage_api_key()
|
||||
{
|
||||
// You can manage your api key only if you are connected via identification.php
|
||||
if (isset($_SESSION['connected_with']) and 'pwg_ui' === $_SESSION['connected_with'])
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
?>
|
||||
|
36
install/db/176-database.php
Normal file
36
install/db/176-database.php
Normal file
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
// +-----------------------------------------------------------------------+
|
||||
// | This file is part of Piwigo. |
|
||||
// | |
|
||||
// | For copyright and license information, please view the COPYING.txt |
|
||||
// | file that was distributed with this source code. |
|
||||
// +-----------------------------------------------------------------------+
|
||||
|
||||
if (!defined('PHPWG_ROOT_PATH'))
|
||||
{
|
||||
die('Hacking attempt!');
|
||||
}
|
||||
|
||||
$upgrade_description = 'Modification to the user_auth_key table to match the api keys';
|
||||
|
||||
// we are modifying the "auth_key" table structure to support the new API key system.
|
||||
// the existing structure was too limited for our needs, this update ensures better
|
||||
// flexibility and security for managing API access tokens in the future.
|
||||
pwg_query(
|
||||
'ALTER TABLE `'.PREFIX_TABLE.'user_auth_keys`
|
||||
ADD COLUMN `apikey_secret` VARCHAR(255) DEFAULT NULL AFTER auth_key,
|
||||
ADD COLUMN `apikey_name` VARCHAR(100) DEFAULT NULL,
|
||||
ADD COLUMN `key_type` VARCHAR(40) DEFAULT NULL,
|
||||
ADD COLUMN `revoked_on` datetime DEFAULT NULL,
|
||||
ADD COLUMN `last_used_on` datetime DEFAULT NULL
|
||||
;');
|
||||
|
||||
// For rows that already exist in the table, we add a key_type
|
||||
pwg_query(
|
||||
'UPDATE `'.PREFIX_TABLE.'user_auth_keys`
|
||||
SET `key_type` = \'auth_key\'
|
||||
WHERE `key_type` IS NULL
|
||||
;');
|
||||
|
||||
echo "\n".$upgrade_description."\n";
|
||||
?>
|
@ -477,3 +477,42 @@ $lang['Choose how you want to see your gallery'] = 'Choose how you want to see y
|
||||
$lang['Change your password'] = 'Change your password';
|
||||
$lang['Options'] = 'Options';
|
||||
$lang['Your changes have been applied.'] = 'Your changes have been applied.';
|
||||
$lang['Create API Keys to secure your acount'] = 'Create API Keys to secure your account';
|
||||
$lang['API Keys'] = 'API Keys';
|
||||
$lang['Created at'] = 'Created at';
|
||||
$lang['Last use'] = 'Last use';
|
||||
$lang['Expires in'] = 'Expires in';
|
||||
$lang['Expired on'] = 'Expired on';
|
||||
$lang['Never'] = 'Never';
|
||||
$lang['New API Key'] = 'New API Key';
|
||||
$lang['Show expired keys'] = 'Show expired keys';
|
||||
$lang['Hide expired keys'] = 'Hide expired keys';
|
||||
$lang['Generate API Key'] = 'Generate API Key';
|
||||
$lang['Create a new API key to secure your account.'] = 'Create a new API key to secure your account.';
|
||||
$lang['API Key name'] = 'API Key name';
|
||||
$lang['Duration'] = 'Duration';
|
||||
$lang['Custom date'] = 'Custom date';
|
||||
$lang['Generate key'] = 'Generate key';
|
||||
$lang['Save your secret Key and ID'] = 'Save your secret Key and ID';
|
||||
$lang['This will not be displayed again. You must copy it to continue.'] = 'This will not be displayed again. You must copy it to continue.';
|
||||
$lang['Done'] = 'Done';
|
||||
$lang['Public key copied.'] = 'Public key copied.';
|
||||
$lang['Secret key copied. Keep it in a safe place.'] = 'Secret key copied. Keep it in a safe place.';
|
||||
$lang['Impossible to copy automatically. Please copy manually.'] = 'Impossible to copy automatically. Please copy manually.';
|
||||
$lang['The api key has been successfully created.'] = 'The API key has been successfully created.';
|
||||
$lang['API Key not found'] = 'API Key not found';
|
||||
$lang['Expired'] = 'Expired';
|
||||
$lang['API Key has been successfully revoked.'] = 'API Key has been successfully revoked.';
|
||||
$lang['API Key has been successfully edited.'] = 'API Key has been successfully edited.';
|
||||
$lang['No expiration'] = 'No expiration';
|
||||
$lang['must not be empty'] = 'must not be empty';
|
||||
$lang['The secret key can no longer be displayed.'] = 'The secret key can no longer be displayed.';
|
||||
$lang['Revoked'] = 'Revoked';
|
||||
$lang['Revoke'] = 'Revoke';
|
||||
$lang['The email %s will be used to notify you when your API key is about to expire.'] = 'The email %s will be used to notify you when your API key is about to expire.';
|
||||
$lang['You have no email address, so you will not be notified when your API key is about to expire.'] = 'You have no email address, so you will not be notified when your API key is about to expire.';
|
||||
$lang['you must choose a date'] = 'you must choose a date';
|
||||
$lang['This API key was manually revoked on %s'] = 'This API key was manually revoked on %s';
|
||||
$lang['Edit API Key'] = 'Edit API Key';
|
||||
$lang['Do you really want to revoke the "%s" API key?'] = 'Do you really want to revoke the "%s" API key?';
|
||||
$lang['To manage your API keys, please log in with your username/password.'] = 'To manage your API keys, please log in with your username/password.';
|
||||
|
@ -476,3 +476,42 @@ $lang['Choose how you want to see your gallery'] = 'Choisissez comment vous voul
|
||||
$lang['Change your password'] = 'Changez votre mot de passe';
|
||||
$lang['Options'] = 'Options';
|
||||
$lang['Your changes have been applied.'] = 'Vos changements ont été pris en compte.';
|
||||
$lang['Create API Keys to secure your acount'] = 'Créez des clés API pour sécuriser votre compte';
|
||||
$lang['API Keys'] = 'Clés API';
|
||||
$lang['Created at'] = 'Crée le';
|
||||
$lang['Last use'] = 'Dernière utilisation';
|
||||
$lang['Expires in'] = 'Expire dans';
|
||||
$lang['Expired on'] = 'Expiré le';
|
||||
$lang['Never'] = 'Jamais';
|
||||
$lang['New API Key'] = 'Nouvelle clé API';
|
||||
$lang['Show expired keys'] = 'Afficher les clés expirées';
|
||||
$lang['Hide expired keys'] = 'Masquer les clés expirées';
|
||||
$lang['Generate API Key'] = 'Générer une clé API';
|
||||
$lang['Create a new API key to secure your account.'] = 'Créez une nouvelle clé API pour sécuriser votre compte.';
|
||||
$lang['API Key name'] = 'Nom de la clé API';
|
||||
$lang['Duration'] = 'Durée';
|
||||
$lang['Custom date'] = 'Date personnalisée';
|
||||
$lang['Generate key'] = 'Générer la clé';
|
||||
$lang['Save your secret Key and ID'] = 'Enregistrez votre clé secrète et votre identifiant';
|
||||
$lang['This will not be displayed again. You must copy it to continue.'] = 'La clé secrete ne sera plus affichée. Vous devez la copier pour continuer.';
|
||||
$lang['Done'] = 'Terminé';
|
||||
$lang['Public key copied.'] = 'Clé publique copiée.';
|
||||
$lang['Secret key copied. Keep it in a safe place.'] = 'Clé secrète copiée. Gardez-la dans un endroit sûr.';
|
||||
$lang['Impossible to copy automatically. Please copy manually.'] = 'Impossible de copier automatiquement. Veuillez copier manuellement.';
|
||||
$lang['The api key has been successfully created.'] = 'La clé API a été créée avec succès.';
|
||||
$lang['API Key not found'] = 'Clé API non trouvée';
|
||||
$lang['Expired'] = 'Expirée';
|
||||
$lang['API Key has been successfully revoked.'] = 'La clé API a été révoquée avec succès.';
|
||||
$lang['API Key has been successfully edited.'] = 'La clé API a été modifiée avec succès.';
|
||||
$lang['No expiration'] = 'Pas d’expiration';
|
||||
$lang['must not be empty'] = 'ne doit pas être vide';
|
||||
$lang['The secret key can no longer be displayed.'] = 'La clé secrète ne peut plus être affichée.';
|
||||
$lang['Revoked'] = 'Révoqué';
|
||||
$lang['Revoke'] = 'Révoquer';
|
||||
$lang['The email <em>%s</em> will be used to notify you when your API key is about to expire.'] = 'L\'email <em>%s</em> sera utilisé pour vous notifier que votre clé API est sur le point d\'expirer.';
|
||||
$lang['You have no email address, so you will not be notified when your API key is about to expire.'] = 'Vous n\'avez pas d\'adresse email, vous ne serez donc pas notifié lorsque votre clé API sera sur le point d\'expirer.';
|
||||
$lang['you must choose a date'] = 'vous devez choisir une date';
|
||||
$lang['This API key was manually revoked on %s'] = 'Cette clé API a été révoquée manuellement le %s';
|
||||
$lang['Edit API Key'] = 'Modifier la clé API';
|
||||
$lang['Do you really want to revoke the "%s" API key?'] = 'Voulez-vous vraiment révoquer la clé API "%s" ?';
|
||||
$lang['To manage your API keys, please log in with your username/password.'] = 'Pour gérer vos clés API, veuillez vous connecter avec votre nom d\'utilisateur/mot de passe.';
|
||||
|
43
profile.php
43
profile.php
@ -342,7 +342,7 @@ function save_profile_from_post($userdata, &$errors)
|
||||
*/
|
||||
function load_profile_in_template($url_action, $url_redirect, $userdata, $template_prefixe=null)
|
||||
{
|
||||
global $template, $conf;
|
||||
global $template, $conf, $user;
|
||||
|
||||
$template->assign('radio_options',
|
||||
array(
|
||||
@ -382,6 +382,47 @@ function load_profile_in_template($url_action, $url_redirect, $userdata, $templa
|
||||
$template->assign('SPECIAL_USER', $special_user);
|
||||
$template->assign('IN_ADMIN', defined('IN_ADMIN'));
|
||||
|
||||
// api key expiration choice
|
||||
list($dbnow) = pwg_db_fetch_row(pwg_query('SELECT ADDDATE(NOW(), INTERVAL 1 DAY);'));
|
||||
$template->assign('API_CURRENT_DATE', explode(' ', $dbnow)[0]);
|
||||
|
||||
$duration = array();
|
||||
$display_duration = array();
|
||||
$has_custom = false;
|
||||
foreach ($conf['api_key_duration'] as $day)
|
||||
{
|
||||
if ('custom' === $day)
|
||||
{
|
||||
$has_custom = true;
|
||||
continue;
|
||||
}
|
||||
$duration[] = 'ADDDATE(NOW(), INTERVAL '.$day.' DAY) as `'.$day.'`';
|
||||
}
|
||||
|
||||
$query = '
|
||||
SELECT
|
||||
'.implode(', ', $duration).'
|
||||
;';
|
||||
$result = query2array($query)[0];
|
||||
foreach ($result as $day => $date)
|
||||
{
|
||||
$display_duration[ $day ] = l10n('%d days', $day) . ' (' . format_date($date, array('day', 'month', 'year')) . ')';
|
||||
}
|
||||
|
||||
if ($has_custom)
|
||||
{
|
||||
$display_duration['custom'] = l10n('Custom date');
|
||||
}
|
||||
$template->assign('API_EXPIRATION', $display_duration);
|
||||
$template->assign('API_SELECTED_EXPIRATION', array_key_first($display_duration));
|
||||
$template->assign('API_CAN_MANAGE', 'pwg_ui' === ($_SESSION['connected_with'] ?? null));
|
||||
|
||||
$email_notifications_infos = $user['email'] ?
|
||||
l10n('The email <em>%s</em> will be used to notify you when your API key is about to expire.', $user['email'])
|
||||
: l10n('You have no email address, so you will not be notified when your API key is about to expire.');
|
||||
$template->assign('API_EMAIL_INFOS', $email_notifications_infos);
|
||||
|
||||
|
||||
// allow plugins to add their own form data to content
|
||||
trigger_notify( 'load_profile_in_template', $userdata );
|
||||
|
||||
|
@ -10,23 +10,14 @@ $(function() {
|
||||
// close
|
||||
element.style.maxHeight = element.scrollHeight + 'px';
|
||||
void element.offsetHeight;
|
||||
element.style.maxHeight = '0px';
|
||||
element.style.maxHeight = '1px';
|
||||
selector.removeClass('open');
|
||||
$(this).addClass('close');
|
||||
} else {
|
||||
// open
|
||||
selector.addClass('open');
|
||||
element.style.maxHeight = element.scrollHeight + 'px';
|
||||
resetSection(display);
|
||||
$(this).removeClass('close');
|
||||
if ('account-display' !== display) {
|
||||
setTimeout(() => {
|
||||
const el = $(`#${display.split('-')[0]}-section`).get(0);
|
||||
el.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'start'
|
||||
});
|
||||
}, 200);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -85,7 +76,6 @@ $(function() {
|
||||
});
|
||||
|
||||
standardSaveSelector.forEach((selector, i) => {
|
||||
// console.log(i, selector);
|
||||
$(selector).on('click', function() {
|
||||
const values = {};
|
||||
$(`#${i}-section`).find('input, textarea, select').each((i, element) => {
|
||||
@ -126,9 +116,79 @@ $(function() {
|
||||
$('#opt_comment').prop('checked', preferencesDefaultValues.opt_comment);
|
||||
$('#opt_hits').prop('checked', preferencesDefaultValues.opt_hits);
|
||||
});
|
||||
|
||||
// API KEY BELOW
|
||||
if (!can_manage_api) {
|
||||
$('.can-manage').hide();
|
||||
$('#cant_manage_api').show();
|
||||
return;
|
||||
};
|
||||
$('#new_apikey').on('click', function() {
|
||||
openApiModal();
|
||||
});
|
||||
|
||||
$('#close_api_modal, #cancel_apikey').on('click', function() {
|
||||
closeApiModal();
|
||||
});
|
||||
|
||||
$('#close_api_modal_edit').on('click', function() {
|
||||
closeApiEditModal();
|
||||
});
|
||||
|
||||
$('#close_api_modal_revoke, #cancel_api_revoke').on('click', function() {
|
||||
closeApiRevokeModal();
|
||||
});
|
||||
|
||||
$('#show_expired_list').on('click', function() {
|
||||
const api_list_expired = $('#api_key_list_expired');
|
||||
const isOpen = $(this).data('show');
|
||||
if(!isOpen) {
|
||||
api_list_expired.get(0).style.maxHeight = 'max-content';
|
||||
$(this).text(str_hide_expired);
|
||||
} else {
|
||||
api_list_expired.get(0).style.maxHeight = '0';
|
||||
$(this).text(str_show_expired);
|
||||
}
|
||||
|
||||
$(this).data('show', !isOpen);
|
||||
|
||||
resetSection('apikey-display', false, true);
|
||||
});
|
||||
|
||||
$(window).on('keydown', function(e) {
|
||||
const haveApiModal = $('#api_modal').is(':visible');
|
||||
const haveApiEditModal = $('#api_modal_edit').is(':visible');
|
||||
const haveApiRevokeModal = $('#api_modal_revoke').is(':visible');
|
||||
if (haveApiModal && e.key === 'Escape') {
|
||||
closeApiModal();
|
||||
}
|
||||
if (haveApiEditModal && e.key === 'Escape') {
|
||||
closeApiEditModal();
|
||||
}
|
||||
if (haveApiRevokeModal && e.key === 'Escape') {
|
||||
closeApiRevokeModal();
|
||||
}
|
||||
});
|
||||
|
||||
$('select[name="api_expiration"]').on('change', function() {
|
||||
const custom_date = $('#api_custom_date');
|
||||
const value = $(this).val();
|
||||
if ('custom' === value) {
|
||||
custom_date.css('display', 'flex');
|
||||
} else {
|
||||
custom_date.css('display', 'none');
|
||||
}
|
||||
$('#error_api_key_date').hide();
|
||||
});
|
||||
|
||||
$('#api_expiration_date').on('change', function() {
|
||||
$('#error_api_key_date').hide();
|
||||
});
|
||||
|
||||
getAllApiKeys();
|
||||
});
|
||||
|
||||
function setInfos(params, method='pwg.users.setMyInfo') {
|
||||
function setInfos(params, method='pwg.users.setMyInfo', callback=null, errCallback=null) {
|
||||
// for debug
|
||||
// console.log('setInfos', params);
|
||||
const all_params = {
|
||||
@ -142,15 +202,359 @@ function setInfos(params, method='pwg.users.setMyInfo') {
|
||||
data: all_params,
|
||||
success: (data) => {
|
||||
if (data.stat == 'ok') {
|
||||
if (typeof callback === 'function') {
|
||||
callback(data.result);
|
||||
return;
|
||||
};
|
||||
pwgToaster({ text: data.result, icon: 'success' });
|
||||
} else if (data.stat == 'fail') {
|
||||
pwgToaster({ text: data.message, icon: 'error' });
|
||||
} else {
|
||||
pwgToaster({ text: 'Error try later...', icon: 'error' });
|
||||
pwgToaster({ text: str_handle_error, icon: 'error' });
|
||||
}
|
||||
if (typeof callback === 'function') {
|
||||
errCallback(data);
|
||||
return;
|
||||
}
|
||||
},
|
||||
error: function (e) {
|
||||
pwgToaster({ text: e.responseJSON?.message ?? 'Server Internal Error try later...', icon: 'error' });
|
||||
pwgToaster({ text: e.responseJSON?.message ?? str_handle_error, icon: 'error' });
|
||||
if (typeof callback === 'function') {
|
||||
errCallback(e);
|
||||
return;
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function getAllApiKeys(reset = false) {
|
||||
$.ajax({
|
||||
url: 'ws.php?format=json&method=pwg.users.api_key.get',
|
||||
type: "POST",
|
||||
dataType: 'json',
|
||||
data: {
|
||||
pwg_token: PWG_TOKEN
|
||||
},
|
||||
success: function(res) {
|
||||
if (res.stat == 'ok') {
|
||||
if (typeof res.result === 'string') {
|
||||
// No keys
|
||||
} else {
|
||||
AddApiLine(res.result, reset);
|
||||
}
|
||||
}
|
||||
},
|
||||
error: function(e) {
|
||||
pwgToaster({ text: e.responseJSON?.message ?? str_handle_error + 'getAllApiKeys', icon: 'error' });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function AddApiLine(lines, reset) {
|
||||
const api_list = $('#api_key_list');
|
||||
const api_list_expired = $('#api_key_list_expired');
|
||||
|
||||
$('#api_key_list .api-tab-line:not(.template-api), #api_key_list .api-tab-collapse:not(.template-api)').remove();
|
||||
$('#api_key_list_expired .api-tab-line:not(.template-api), #api_key_list_expired .api-tab-collapse:not(.template-api)').remove();
|
||||
|
||||
lines.forEach((line, i) => {
|
||||
const api_line = $('#api_line').clone();
|
||||
const api_collapse = $('#api_collapse').clone();
|
||||
const tmp_id = line.auth_key.slice(24, 34);
|
||||
|
||||
api_line.removeClass('template-api').addClass('api-tab');
|
||||
api_line.attr('id', `api_${tmp_id}`);
|
||||
api_line.find('.icon-collapse').data('api', tmp_id);
|
||||
api_line.find('.api_name').text(line.apikey_name).attr('title', line.apikey_name);
|
||||
api_line.find('.api_creation').text(line.created_on_format);
|
||||
api_line.find('.api_last_use').text(line.last_used_on_since).attr('title', line.last_used_on_since);
|
||||
api_line.find('.api_expiration').text(line.expiration);
|
||||
api_line.find('.api-icon-action').attr('data-api', `api_${tmp_id}`);
|
||||
api_line.find('.api-icon-action').attr('data-pkid', line.auth_key);
|
||||
|
||||
api_collapse.attr('id', `api_collapse_${tmp_id}`);
|
||||
api_collapse.removeClass('template-api');
|
||||
api_collapse.find('.api_key').text(line.auth_key);
|
||||
api_collapse.find('.icon-clone').attr({
|
||||
'data-copy': line.auth_key,
|
||||
'data-success': `api_copy_success_${tmp_id}`
|
||||
});
|
||||
api_collapse.find('.api-copy').attr('id', `api_copy_success_${tmp_id}`);
|
||||
|
||||
if (!line.revoked_on && !line.is_expired) {
|
||||
api_list.append(api_line);
|
||||
api_line.after(api_collapse);
|
||||
} else {
|
||||
api_list_expired.append(api_line);
|
||||
api_line.after(api_collapse);
|
||||
api_line.find('.api-icon-action').remove();
|
||||
if (line.is_expired) {
|
||||
api_line.find('.api_expiration').html(`<i class="gallery-icon-skull api-skull"></i> <span data-tooltip="${line.expired_on_format}">${line.expired_on_since}</span>`);
|
||||
} else {
|
||||
api_line.find('.api_expiration').html(`<i class="gallery-icon-skull api-skull"></i> <span>${/\d/.test(line.revoked_on_since) ? line.revoked_on_since : no_time_elapsed}</span> <i data-tooltip="${line.revoked_on_message}" class="icon-info-circled-1 api-info"></i>`);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
apiLineEvent();
|
||||
if (reset) {
|
||||
resetSection('apikey-display');
|
||||
}
|
||||
}
|
||||
|
||||
function apiLineEvent() {
|
||||
$('.icon-collapse').off('click').on('click', function() {
|
||||
const api_collapse = $(`#api_collapse_${$(this).data('api')}`);
|
||||
const api_line = $(`#api_${$(this).data('api')}`);
|
||||
|
||||
if (api_collapse.is(':visible')) {
|
||||
api_collapse.removeClass('open');
|
||||
api_line.removeClass('open');
|
||||
api_line.find('.icon-collapse').addClass('close');
|
||||
api_collapse.css('display', 'none');
|
||||
api_collapse.find('.api-copy').addClass('api-hide');
|
||||
} else {
|
||||
api_collapse.addClass('open');
|
||||
api_line.addClass('open');
|
||||
api_line.find('.icon-collapse').removeClass('close');
|
||||
api_collapse.css('display', 'grid');
|
||||
}
|
||||
|
||||
resetSection('apikey-display', false, true);
|
||||
});
|
||||
|
||||
$('.api-tab-collapse .icon-clone').off('click').on('click', function() {
|
||||
const data_to_copy = $(this).data('copy');
|
||||
const selector = $(this).data('success');
|
||||
copyToClipboard(data_to_copy, str_copy_key_id, `#${selector}`);
|
||||
});
|
||||
|
||||
$('.api-tab-line .edit-mode').off('click').on('click', function() {
|
||||
const selector = $(this).parent().data('api');
|
||||
openApiEditModal(`#${selector}`);
|
||||
});
|
||||
|
||||
$('.api-tab-line .delete-mode').off('click').on('click', function() {
|
||||
const selector = $(this).parent().data('api');
|
||||
openApiRevokeModal(`#${selector}`);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
function resetSection(selector, scroll = true, maxContent = false) {
|
||||
const el = $(`#${selector}`);
|
||||
const element = el.get(0);
|
||||
const scrollH = maxContent ? 'max-content' : element.scrollHeight + 'px';
|
||||
element.style.maxHeight = scrollH;
|
||||
|
||||
if ('account-display' !== selector && scroll) {
|
||||
setTimeout(() => {
|
||||
const el = $(`#${selector.split('-')[0]}-section`).get(0);
|
||||
el.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'start'
|
||||
});
|
||||
}, 200);
|
||||
}
|
||||
}
|
||||
|
||||
function openApiModal() {
|
||||
$('#api_modal').fadeIn();
|
||||
$('#api_key_name').trigger('focus');
|
||||
saveApiKeyEvent();
|
||||
}
|
||||
|
||||
function closeApiModal() {
|
||||
$('#api_modal').fadeOut(() => {
|
||||
$('#api_key_name').val('');
|
||||
$('select[name="api_expiration"]').val(selected_date).trigger('change');
|
||||
$('#api_expiration_date').val('');
|
||||
|
||||
$('#api_secret_key').val('');
|
||||
$('#retrieves_keyapi').hide();
|
||||
$('#generate_keyapi').show();
|
||||
$('#done_apikey').attr('disabled', true);
|
||||
$('#api_key_copy_success, #api_id_copy_success').addClass('api-hide');
|
||||
});
|
||||
unbindApiKeyEvents();
|
||||
}
|
||||
|
||||
function successApiModal(secret, id) {
|
||||
$('#api_secret_key').val(secret);
|
||||
$('#api_id_key').val(id);
|
||||
|
||||
$('#generate_keyapi').hide();
|
||||
$('#retrieves_keyapi').fadeIn();
|
||||
|
||||
$('#api_secret_copy').off('click').on('click', function() {
|
||||
const copy = copyToClipboard(secret, str_copy_key_secret, '#api_key_copy_success');
|
||||
|
||||
$('#done_apikey').removeAttr('disabled');
|
||||
$('#done_apikey').on('click', closeApiModal);
|
||||
});
|
||||
|
||||
$('#api_id_copy').off('click').on('click', function() {
|
||||
const copy = copyToClipboard(id, str_copy_key_id, '#api_id_copy_success');
|
||||
});
|
||||
}
|
||||
|
||||
//api edit modal
|
||||
function openApiEditModal(selector) {
|
||||
const value = $(selector).find('.api_name').text();
|
||||
const pkid = $(selector).find('.api-icon-action').data('pkid');
|
||||
$('#api_key_edit').val(value);
|
||||
$('#api_modal_edit').fadeIn();
|
||||
$('#api_key_edit').trigger('focus');
|
||||
saveApiEditEvents(pkid);
|
||||
}
|
||||
|
||||
function closeApiEditModal() {
|
||||
$('#api_modal_edit').fadeOut(() => {
|
||||
$('#api_key_edit').val('');
|
||||
unbindApiEditEvents();
|
||||
});
|
||||
}
|
||||
|
||||
function saveApiEditEvents(pkid) {
|
||||
$('#save_api_edit').on('click', function() {
|
||||
const value = $('#api_key_edit').val();
|
||||
|
||||
if ('' == value) {
|
||||
$('#error_api_key_edit').show();
|
||||
return;
|
||||
}
|
||||
setInfos(
|
||||
{
|
||||
pkid,
|
||||
key_name: value,
|
||||
},
|
||||
'pwg.users.api_key.edit',
|
||||
(res) => {
|
||||
pwgToaster({ text: str_api_edited, icon: 'success' });
|
||||
getAllApiKeys(true);
|
||||
closeApiEditModal();
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function unbindApiEditEvents() {
|
||||
$('#save_api_edit').off('click');
|
||||
}
|
||||
|
||||
// api revoke modal
|
||||
function openApiRevokeModal(selector) {
|
||||
const apiName = $(selector).find('.api_name').text();
|
||||
const pkid = $(selector).find('.api-icon-action').data('pkid');
|
||||
const text = sprintf(str_revoke_key, apiName);
|
||||
$('#api_modal_revoke_title').text(text);
|
||||
|
||||
$('#api_modal_revoke').fadeIn();
|
||||
saveApiRevokeEvents(pkid);
|
||||
}
|
||||
|
||||
function closeApiRevokeModal() {
|
||||
$('#api_modal_revoke').fadeOut(() => {
|
||||
$('#api_modal_revoke_title').text('');
|
||||
unbindApiRevokeEvents();
|
||||
});
|
||||
}
|
||||
|
||||
function saveApiRevokeEvents(pkid) {
|
||||
$('#revoke_api_key').on('click', function() {
|
||||
setInfos(
|
||||
{
|
||||
pkid,
|
||||
},
|
||||
'pwg.users.api_key.revoke',
|
||||
(res) => {
|
||||
pwgToaster({ text: str_api_revoked, icon: 'success' });
|
||||
getAllApiKeys(true);
|
||||
closeApiRevokeModal();
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function unbindApiRevokeEvents() {
|
||||
$('#revoke_api_key').off('click');
|
||||
}
|
||||
|
||||
function copyToClipboard(copy, message, selector = null) {
|
||||
if (window.isSecureContext && navigator.clipboard) {
|
||||
navigator.clipboard.writeText(copy);
|
||||
if (selector) {
|
||||
$(selector).removeClass('api-hide');
|
||||
// auto hide
|
||||
// setTimeout(() => {
|
||||
// $(selector).addClass('api-hide');
|
||||
// }, 1000);
|
||||
} else {
|
||||
pwgToaster({ text: message, icon: 'success' });
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
pwgToaster({ text: str_cant_copy, icon: 'error' });
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function saveApiKeyEvent() {
|
||||
const handler = () => {
|
||||
const api_name = $('#api_key_name').val();
|
||||
let api_duration = $('select[name="api_expiration"]').val();
|
||||
|
||||
if (api_name == '') {
|
||||
$('#error_api_key_name').show();
|
||||
return;
|
||||
}
|
||||
|
||||
if ('custom' === api_duration && !$('#api_expiration_date').val()) {
|
||||
$('#error_api_key_date').show();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
unbindApiKeyEvents();
|
||||
|
||||
if ('custom' === api_duration) {
|
||||
const today = new Date();
|
||||
const custom_date = new Date($('#api_expiration_date').val());
|
||||
const one_day = 1000 * 60 * 60 * 24;
|
||||
const days = Math.ceil((custom_date.getTime() - today.getTime() ) / (one_day));
|
||||
api_duration = days;
|
||||
} else {
|
||||
api_duration = Number(api_duration) ?? 1;
|
||||
}
|
||||
|
||||
setInfos(
|
||||
{
|
||||
key_name: api_name,
|
||||
duration: api_duration
|
||||
},
|
||||
'pwg.users.api_key.create',
|
||||
(res) => {
|
||||
pwgToaster({ text: str_api_added, icon: 'success' });
|
||||
getAllApiKeys(true);
|
||||
successApiModal(res.apikey_secret, res.auth_key);
|
||||
},
|
||||
(err) => {
|
||||
saveApiKeyEvent();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
$('#save_apikey').on('click.apikey', handler);
|
||||
$(window).on('keydown.apikey', function(e) {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
handler();
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function unbindApiKeyEvents() {
|
||||
$('#api_modal').find('*').addBack().off('.apikey');
|
||||
$(window).off('.apikey');
|
||||
}
|
||||
|
@ -3,21 +3,39 @@
|
||||
{combine_css path="admin/themes/default/fontello/css/fontello.css" order=-11}
|
||||
|
||||
<script>
|
||||
var selected_language = `{$language_options[$current_language]}`;
|
||||
var selected_language = `{$language_options[$current_language]}`;
|
||||
var url_logo_light = `{$ROOT_URL}themes/standard_pages/images/piwigo_logo.svg`;
|
||||
var url_logo_dark = `{$ROOT_URL}themes/standard_pages/images/piwigo_logo_dark.svg`;
|
||||
</script>
|
||||
{combine_script id='standard_pages_js' load='async' require='jquery' path='themes/standard_pages/js/standard_pages.js'}
|
||||
{combine_script id='standard_profile_js' load='async' require='jquery' path='themes/standard_pages/js/profile.js'}
|
||||
{combine_script id='common' load='footer' require='jquery' path='admin/themes/default/js/common.js'}
|
||||
{footer_script}
|
||||
const standardSaveSelector = [];
|
||||
const preferencesDefaultValues = {
|
||||
nb_image_page: {$DEFAULT_USER_VALUES['nb_image_page']},
|
||||
recent_period: {$DEFAULT_USER_VALUES['recent_period']},
|
||||
opt_album: {$DEFAULT_USER_VALUES['expand']},
|
||||
opt_comment: {$DEFAULT_USER_VALUES['show_nb_comments']},
|
||||
opt_hits: {$DEFAULT_USER_VALUES['show_nb_hits']},
|
||||
nb_image_page: {$DEFAULT_USER_VALUES['nb_image_page']},
|
||||
recent_period: {$DEFAULT_USER_VALUES['recent_period']},
|
||||
opt_album: {$DEFAULT_USER_VALUES['expand']},
|
||||
opt_comment: {$DEFAULT_USER_VALUES['show_nb_comments']},
|
||||
opt_hits: {$DEFAULT_USER_VALUES['show_nb_hits']},
|
||||
};
|
||||
const selected_date = "{$API_SELECTED_EXPIRATION}";
|
||||
const can_manage_api = {($API_CAN_MANAGE) ? "true" : "false"};
|
||||
|
||||
const str_copy_key_id = "{"Public key copied."|translate|escape:javascript}";
|
||||
const str_copy_key_secret = "{"Secret key copied. Keep it in a safe place."|translate|escape:javascript}";
|
||||
const str_cant_copy = "{"Impossible to copy automatically. Please copy manually."|translate|escape:javascript}";
|
||||
const str_api_added = "{"The api key has been successfully created."|translate|escape:javascript}";
|
||||
const str_revoked = "{"Revoked"|translate|escape:javascript}";
|
||||
const str_show_expired = "{"Show expired keys"|translate|escape:javascript}";
|
||||
const str_hide_expired = "{"Hide expired keys"|translate|escape:javascript}";
|
||||
const str_handle_error = "{"An error has occured"|translate|escape:javascript}";
|
||||
const str_expires_in = "{"Expires in"|translate|escape:javascript}";
|
||||
const str_expires_on = "{"Expired on"|translate|escape:javascript}";
|
||||
const str_revoke_key = "{'Do you really want to revoke the "%s" API key?'|translate|escape:javascript}";
|
||||
const str_api_revoked = "{"API Key has been successfully revoked."|translate|escape:javascript}";
|
||||
const str_api_edited = "{"API Key has been successfully edited."|translate|escape:javascript}";
|
||||
const no_time_elapsed = "{"right now"|translate|escape:javascript}";
|
||||
{/footer_script}
|
||||
|
||||
<container id="mode" class="light">
|
||||
@ -28,7 +46,6 @@ const preferencesDefaultValues = {
|
||||
</div>
|
||||
<div>
|
||||
<a href="{$HELP_LINK}" target="_blank">{'Help'|translate}</a>
|
||||
{include file='toaster.tpl'}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@ -49,7 +66,7 @@ const preferencesDefaultValues = {
|
||||
</div>
|
||||
<div class="form" id="account-display">
|
||||
<div class="column-flex first">
|
||||
<label for="username">{'Username'|translate}</label>
|
||||
<label>{'Username'|translate}</label>
|
||||
<div class="row-flex input-container username">
|
||||
<i class="gallery-icon-user"></i>
|
||||
<p>{$USERNAME}</p>
|
||||
@ -57,9 +74,9 @@ const preferencesDefaultValues = {
|
||||
</div>
|
||||
</div>
|
||||
<div class="column-flex">
|
||||
<label for="mail_address">{'Email address'|translate}</label>
|
||||
<label for="email">{'Email address'|translate}</label>
|
||||
<div class="row-flex input-container">
|
||||
<i class="gallery-icon-user"></i>
|
||||
<i class="icon-mail-alt"></i>
|
||||
<input type="email" name="mail_address" id="email" value="{$EMAIL}" />
|
||||
</div>
|
||||
<p id="email_error" class="error-message"><i class="gallery-icon-attention-circled"></i>
|
||||
@ -94,7 +111,7 @@ const preferencesDefaultValues = {
|
||||
</div>
|
||||
|
||||
<div class="column-flex">
|
||||
<label for="theme">{'Theme'|translate}</label>
|
||||
<label>{'Theme'|translate}</label>
|
||||
<div class="row-flex input-container">
|
||||
<i class="icon-brush"></i>
|
||||
{html_options name=theme options=$template_options selected=$template_selection}
|
||||
@ -103,7 +120,7 @@ const preferencesDefaultValues = {
|
||||
</div>
|
||||
|
||||
<div class="column-flex">
|
||||
<label for="language">{'Language'|translate}</label>
|
||||
<label>{'Language'|translate}</label>
|
||||
<div class="row-flex input-container">
|
||||
<i class="icon-language"></i>
|
||||
{html_options name=language options=$language_options selected=$language_selection}
|
||||
@ -212,6 +229,205 @@ const preferencesDefaultValues = {
|
||||
</section>
|
||||
{/if}
|
||||
|
||||
{* API KEY *}
|
||||
<section id="apikey-section" class="profile-section">
|
||||
<div class="title">
|
||||
<div class="column-flex">
|
||||
<h1>{'API Keys'|translate}</h1>
|
||||
<p>{'Create API Keys to secure your acount'|translate}</p>
|
||||
</div>
|
||||
<i class="gallery-icon-up-open display-btn close" data-display="apikey-display"></i>
|
||||
</div>
|
||||
|
||||
<div class="form" id="apikey-display">
|
||||
<div class="api-cant-manage" id="cant_manage_api">
|
||||
<p>{'To manage your API keys, please log in with your username/password.'|translate|escape:html}</p>
|
||||
</div>
|
||||
|
||||
<div class="new-apikey can-manage">
|
||||
<button class="btn btn-main" id="new_apikey">{'New API Key'|translate}</button>
|
||||
</div>
|
||||
<div class="api-list can-manage">
|
||||
<div class="api-list-head api-tab">
|
||||
<div aria-hidden="true"></div>
|
||||
<p>{'API Key name'|translate}</p>
|
||||
<p>{'Created at'|translate}</p>
|
||||
<p>{'Last use'|translate}</p>
|
||||
<p id="api_expires_in">{'Expires in'|translate}</p>
|
||||
<div aria-hidden="true"></div>
|
||||
</div>
|
||||
<div class="api-list-body" id="api_key_list">
|
||||
|
||||
<div class="api-tab-line border-line template-api" id="api_line">
|
||||
<div class="api-icon-collapse">
|
||||
<i class="gallery-icon-up-open icon-collapse close" data-api=""></i>
|
||||
</div>
|
||||
<p class="api_name"></p>
|
||||
<p class="api_creation"></p>
|
||||
<p class="api_last_use"></p>
|
||||
<p class="api_expiration"></p>
|
||||
<div class="api-icon-action row-flex" data-api="" data-pkid="">
|
||||
<i class="icon-pencil edit-mode"></i>
|
||||
<i class="icon-trash-1 delete-mode"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="api-tab-collapse border-line template-api" style="display: none;" id="api_collapse">
|
||||
<div aria-hidden="true"></div>
|
||||
<div class="keys">
|
||||
<div class="row-flex key">
|
||||
<i class="gallery-icon-hash"></i>
|
||||
<p class="api_key"></p>
|
||||
<i class="icon-clone" data-copy="" data-success=""></i>
|
||||
<p id="" class="api-copy api-hide success-message">{"Public key copied."|translate|escape:html}</p>
|
||||
</div>
|
||||
<div class="row-flex key">
|
||||
<i class="icon-key"></i>
|
||||
<p>{"The secret key can no longer be displayed."|translate}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="new-apikey">
|
||||
<button class="btn btn-link" id="show_expired_list" data-show="false">{'Show expired keys'|translate}</button>
|
||||
</div>
|
||||
<div class="api-list-body" id="api_key_list_expired">
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{* API KEY MODAL *}
|
||||
<div class="bg-modal" id="api_modal">
|
||||
<div class="body-modal">
|
||||
<a class="icon-cancel close-modal" id="close_api_modal"></a>
|
||||
|
||||
<div id="generate_keyapi">
|
||||
<div class="head-modal">
|
||||
<p class="title-modal">{'Generate API Key'|translate}</p>
|
||||
<p class="subtitle-modal">{'Create a new API key to secure your account.'|translate}</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="column-flex first">
|
||||
<label for="api_key_name">{'API Key name'|translate}</label>
|
||||
<div class="row-flex input-container">
|
||||
<i class="icon-key"></i>
|
||||
<input type="text" id="api_key_name" />
|
||||
</div>
|
||||
<p id="error_api_key_name" class="error-message"><i class="gallery-icon-attention-circled"></i>
|
||||
{'must not be empty'|translate}</p>
|
||||
</div>
|
||||
|
||||
<div class="row-flex section-expiration">
|
||||
<div class="column-flex">
|
||||
<label>{'Duration'|translate}</label>
|
||||
<div class="row-flex input-container api-expiration">
|
||||
<i class="gallery-icon-calendar"></i>
|
||||
{html_options name=api_expiration options=$API_EXPIRATION}
|
||||
</div>
|
||||
<p id="error_api_key_date" class="error-message"><i class="gallery-icon-attention-circled"></i>
|
||||
{'you must choose a date'|translate}</p>
|
||||
</div>
|
||||
|
||||
<div class="column-flex" id="api_custom_date">
|
||||
<label for="api_expiration_date">{'Custom date'|translate}</label>
|
||||
<div class="row-flex input-container api-expiration">
|
||||
<input type="date" id="api_expiration_date" name="api_expiration_custom" min="{$API_CURRENT_DATE}" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="api-mail-infos">{$API_EMAIL_INFOS}</p>
|
||||
|
||||
<div class="save">
|
||||
<button class="btn btn-cancel" id="cancel_apikey">{'Cancel'|translate}</button>
|
||||
<button class="btn btn-main" id="save_apikey">{'Generate key'|translate}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="retrieves_keyapi">
|
||||
<div class="head-modal">
|
||||
<p class="title-modal">{'Generate API Key'|translate}</p>
|
||||
<p class="subtitle-modal">{'Save your secret Key and ID'|translate}</p>
|
||||
<p class="modal-secret">{'This will not be displayed again. You must copy it to continue.'|translate}
|
||||
<p>
|
||||
</div>
|
||||
|
||||
<div class="modal-input-keys">
|
||||
<p id="api_id_copy_success" class="api-copy api-hide success-message">
|
||||
{"Public key copied."|translate|escape:html}</p>
|
||||
</div>
|
||||
<div class="input-modal input-modal-id row-flex">
|
||||
<i class="gallery-icon-hash"></i>
|
||||
<input type="text" id="api_id_key" />
|
||||
<i class="icon-clone" id="api_id_copy"></i>
|
||||
</div>
|
||||
|
||||
<div class="modal-input-keys">
|
||||
<p id="api_key_copy_success" class="modal-input-key api-copy api-hide success-message">
|
||||
{"Secret key copied. Keep it in a safe place."|translate|escape:html}</p>
|
||||
</div>
|
||||
<div class="input-modal input-modal-key row-flex">
|
||||
<i class="icon-key"></i>
|
||||
<input type="text" id="api_secret_key" />
|
||||
<i class="icon-clone" id="api_secret_copy"></i>
|
||||
</div>
|
||||
|
||||
<div class="save">
|
||||
<button class="btn btn-main" id="done_apikey" disabled>{'Done'|translate}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{* API KEY MODAL EDIT *}
|
||||
<div class="bg-modal" id="api_modal_edit">
|
||||
<div class="body-modal">
|
||||
<a class="icon-cancel close-modal" id="close_api_modal_edit"></a>
|
||||
|
||||
<div>
|
||||
<div class="head-modal">
|
||||
<p class="title-modal">{'Edit API Key'|translate}</p>
|
||||
</div>
|
||||
|
||||
<div class="column-flex first">
|
||||
<label for="api_key_edit">{'API Key name'|translate}</label>
|
||||
<div class="row-flex input-container">
|
||||
<i class="icon-key"></i>
|
||||
<input type="text" id="api_key_edit" />
|
||||
</div>
|
||||
<p id="error_api_key_edit" class="error-message"><i class="gallery-icon-attention-circled"></i>
|
||||
{'must not be empty'|translate}</p>
|
||||
</div>
|
||||
|
||||
<div class="save">
|
||||
<button class="btn btn-main" id="save_api_edit">{'Save'|translate}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{* API KEY MODAL REVOKE *}
|
||||
<div class="bg-modal" id="api_modal_revoke">
|
||||
<div class="body-modal">
|
||||
<a class="icon-cancel close-modal" id="close_api_modal_revoke"></a>
|
||||
|
||||
<div>
|
||||
<div class="head-modal">
|
||||
<p class="title-modal" id="api_modal_revoke_title"></p>
|
||||
</div>
|
||||
|
||||
<div class="save">
|
||||
<button class="btn btn-cancel" id="cancel_api_revoke">{'Cancel'|translate}</button>
|
||||
<button class="btn btn-main btn-revoked" id="revoke_api_key">{'Revoke'|translate}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{if isset($PLUGINS_PROFILE)}
|
||||
{foreach from=$PLUGINS_PROFILE item=plugin_block key=k_block}
|
||||
<section id="{$k_block}-section" class="profile-section">
|
||||
@ -225,12 +441,12 @@ const preferencesDefaultValues = {
|
||||
<div class="form plugins" id="{$k_block}-display">
|
||||
{include file=$plugin_block.template}
|
||||
{if $plugin_block.standard_show_save}
|
||||
<div class="save">
|
||||
<button class="btn btn-main" id="save_{$k_block}">{'Submit'|translate}</button>
|
||||
</div>
|
||||
{footer_script}
|
||||
<div class="save">
|
||||
<button class="btn btn-main" id="save_{$k_block}">{'Submit'|translate}</button>
|
||||
</div>
|
||||
{footer_script}
|
||||
standardSaveSelector.push('#save_{$k_block}');
|
||||
{/footer_script}
|
||||
{/footer_script}
|
||||
{/if}
|
||||
</div>
|
||||
</section>
|
||||
@ -252,4 +468,5 @@ const preferencesDefaultValues = {
|
||||
</div>
|
||||
</section>
|
||||
{/if}
|
||||
{include file='toaster.tpl'}
|
||||
</container>
|
@ -5,13 +5,14 @@
|
||||
}
|
||||
|
||||
.toaster {
|
||||
position: absolute;
|
||||
position: fixed;
|
||||
right: 15px;
|
||||
max-width: 300px;
|
||||
top: 40px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.toast {
|
||||
@ -29,14 +30,24 @@
|
||||
font-size: 33px;
|
||||
}
|
||||
|
||||
.toast.success {
|
||||
background-color:#4CA530;
|
||||
color:#D6FFCF;
|
||||
.light .toast.success {
|
||||
background-color: #D6FFCF;
|
||||
color: #4CA530;
|
||||
}
|
||||
|
||||
.toast.error {
|
||||
background-color:#BE4949;
|
||||
color:#FFC8C8;
|
||||
.light .toast.error {
|
||||
background-color: #F8D7DC;
|
||||
color: #EB3D33;
|
||||
}
|
||||
|
||||
.dark .toast.success {
|
||||
background-color: #4EA590;
|
||||
color: #AAF6E4;
|
||||
}
|
||||
|
||||
.dark .toast.error {
|
||||
background-color: #BE4949;
|
||||
color: #FFC8C8;
|
||||
}
|
||||
{/html_style}
|
||||
<div class="toaster" id="pwg_toaster">
|
||||
|
@ -9,10 +9,18 @@ html{
|
||||
|
||||
#theHeader,
|
||||
#copyright,
|
||||
.template-section{
|
||||
#api_custom_date,
|
||||
#retrieves_keyapi,
|
||||
.template-section,
|
||||
.template-api,
|
||||
.api_name_edit {
|
||||
display:none;
|
||||
}
|
||||
|
||||
.api-hide {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
#theIdentificationPage,
|
||||
#theRegisterPage,
|
||||
#thePasswordPage,
|
||||
@ -95,7 +103,8 @@ h1 i{
|
||||
bottom:0;
|
||||
}
|
||||
|
||||
.input-container{
|
||||
.input-container,
|
||||
.input-modal {
|
||||
border-radius:3px;
|
||||
padding:5px 15px;
|
||||
margin-bottom:25px;
|
||||
@ -103,6 +112,7 @@ h1 i{
|
||||
}
|
||||
|
||||
.input-container input,
|
||||
.input-modal input,
|
||||
.input-container select,
|
||||
.input-container textarea{
|
||||
background-color:transparent;
|
||||
@ -129,6 +139,8 @@ input[type='radio'] {
|
||||
}
|
||||
|
||||
.input-container input:focus,
|
||||
.input-modal input:focus,
|
||||
.profile-section .api-tab-line.edit input:focus,
|
||||
.input-container select:focus,
|
||||
.input-container textarea:focus{
|
||||
border:none;
|
||||
@ -139,11 +151,13 @@ select {
|
||||
padding: 5px 0;
|
||||
}
|
||||
|
||||
.input-container:focus-within{
|
||||
.input-container:focus-within,
|
||||
.input-modal:focus-within{
|
||||
border:1px solid #ff7700!important;
|
||||
}
|
||||
|
||||
.input-container i {
|
||||
.input-container i,
|
||||
.input-modal i {
|
||||
font-size:15px;
|
||||
margin-right:5px;
|
||||
}
|
||||
@ -226,11 +240,36 @@ p.form-instructions{
|
||||
color: #3C3C3C!important;
|
||||
}
|
||||
|
||||
.btn-cancel,
|
||||
.btn-link {
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font: inherit;
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
line-height: 1;
|
||||
height: fit-content;
|
||||
}
|
||||
|
||||
.btn-link {
|
||||
text-decoration: underline;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
a.btn-main{
|
||||
display:block;
|
||||
text-align:center;
|
||||
}
|
||||
|
||||
.btn-main:disabled {
|
||||
background-color:#aaaaaa!important;
|
||||
color: #3C3C3C !important;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
#return-to-gallery{
|
||||
margin: 30px auto;
|
||||
display:block;
|
||||
@ -352,14 +391,19 @@ a.btn-main{
|
||||
.error-message{
|
||||
text-align: left;
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
bottom: 5px;
|
||||
left:0;
|
||||
margin: 0;
|
||||
display:none;
|
||||
}
|
||||
|
||||
#error_api_key_date.error-message {
|
||||
bottom: -20px;
|
||||
}
|
||||
|
||||
.error-message i,
|
||||
p.error-message{
|
||||
p.error-message,
|
||||
.modal-secret {
|
||||
color: #EB3223!important;
|
||||
}
|
||||
|
||||
@ -417,7 +461,7 @@ p.error-message{
|
||||
margin-top: 0;
|
||||
max-height: 0;
|
||||
overflow: hidden;
|
||||
transition: max-height 0.2s ease;
|
||||
transition: max-height 0.6s ease;
|
||||
}
|
||||
|
||||
.profile-section .form.open {
|
||||
@ -430,13 +474,22 @@ p.error-message{
|
||||
/* gap: 15px; */
|
||||
}
|
||||
|
||||
.profile-section .save {
|
||||
.profile-section .save,
|
||||
.profile-section .new-apikey,
|
||||
.profile-section .modal-input-keys {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.profile-section .modal-input-keys {
|
||||
position: absolute;
|
||||
right: 30px;
|
||||
}
|
||||
|
||||
.profile-section .save .btn-main,
|
||||
.profile-section .new-apikey .btn-main,
|
||||
.profile-section .reset .btn-main {
|
||||
padding: 10px 35px;
|
||||
}
|
||||
@ -445,7 +498,10 @@ p.error-message{
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.gallery-icon-up-open {
|
||||
.gallery-icon-up-open:not(
|
||||
.api-list .gallery-icon-up-open,
|
||||
#api_key_list_expired .gallery-icon-up-open
|
||||
) {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
cursor: pointer;
|
||||
@ -453,10 +509,23 @@ p.error-message{
|
||||
transition: transform 0.5s ease;
|
||||
}
|
||||
|
||||
.gallery-icon-up-open.close {
|
||||
.gallery-icon-up-open.close,
|
||||
.profile-section .icon-collapse.close {
|
||||
position: relative;
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.profile-section .api-icon-collapse .icon-collapse.close {
|
||||
top: 2px;
|
||||
left: -0.2px;
|
||||
}
|
||||
|
||||
.profile-section .icon-collapse {
|
||||
display: inline-block;
|
||||
transition: transform 0.4s;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.profile-section .username {
|
||||
width: fit-content;
|
||||
cursor: not-allowed;
|
||||
@ -464,10 +533,17 @@ p.error-message{
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
.profile-section .input-container.radio {
|
||||
.profile-section .input-container.radio,
|
||||
.profile-section .section-expiration,
|
||||
.profile-section .api-icon-action {
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.profile-section .api-icon-action {
|
||||
padding-right: 10px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.profile-section .input-container.radio label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@ -487,6 +563,227 @@ p.error-message{
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.profile-section .api-tab {
|
||||
display: grid;
|
||||
grid-template-columns: 60px 2fr 1fr 1fr 1fr 0.5fr;
|
||||
/* grid-template-columns: 60px 200px 100px 100px 100px 30px; */
|
||||
justify-items: start;
|
||||
align-items: center;
|
||||
max-height: 40px;
|
||||
}
|
||||
|
||||
.profile-section .api-list-head {
|
||||
padding: 15px 0;
|
||||
border-radius: 8px;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.profile-section .api-expiration {
|
||||
width: fit-content;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.profile-section .api-mail-infos {
|
||||
position: relative;
|
||||
font-size: 12px;
|
||||
padding-top: 20px;
|
||||
margin-bottom: 25px;
|
||||
text-align: start;
|
||||
}
|
||||
|
||||
.profile-section .api-icon-collapse {
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.profile-section .api-icon-action i:hover,
|
||||
.close-modal:hover,
|
||||
.profile-section .icon-clone:hover {
|
||||
color: #ff7700;
|
||||
}
|
||||
|
||||
.profile-section .api-tab-line,
|
||||
.profile-section .api-tab-collapse {
|
||||
padding: 10px 0;
|
||||
border-radius: 8px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.profile-section .api-tab-line.open {
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
.profile-section .api-tab-collapse.open {
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
}
|
||||
|
||||
.profile-section .api-tab-line i:not(#api_key_list_expired, .api_expiration .api-skull),
|
||||
.profile-section .icon-clone {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.profile-section div.api-tab-line:nth-child(even) {
|
||||
background-color: #303030;
|
||||
}
|
||||
.profile-section .api-tab-collapse .key {
|
||||
gap: 10px;
|
||||
padding: 5px 0;
|
||||
}
|
||||
|
||||
.profile-section .api-tab-collapse {
|
||||
padding-bottom: 20px;
|
||||
display: grid;
|
||||
grid-template-columns: 60px auto;
|
||||
}
|
||||
|
||||
.profile-section .api_name,
|
||||
.profile-section .api_creation,
|
||||
.profile-section .api_expiration {
|
||||
text-overflow: ellipsis;
|
||||
max-width: 90%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.profile-section #api_key_list_expired .api_expiration {
|
||||
text-overflow: unset;
|
||||
max-width: unset;
|
||||
}
|
||||
|
||||
.profile-section #api_key_list .border-line {
|
||||
border: 1px solid transparent !important;
|
||||
}
|
||||
|
||||
.profile-section #api_key_list .edit {
|
||||
border: 1px solid #ff7700 !important;
|
||||
}
|
||||
|
||||
.profile-section .api-list-head > p{
|
||||
text-align: start !important;
|
||||
}
|
||||
|
||||
.profile-section .api_last_use {
|
||||
max-width: 98%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.profile-section .api_name_edit {
|
||||
width: max-content;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
.profile-section .new-apikey .btn-link{
|
||||
color: #9A9A9A !important;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.profile-section .api-copy {
|
||||
padding: 1px 10px;
|
||||
width: fit-content;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.profile-section #show_expired_list {
|
||||
margin: 15px 0;
|
||||
}
|
||||
|
||||
.profile-section #api_key_list_expired {
|
||||
max-height: 0;
|
||||
transition: max-height 0.3s ease;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.profile-section .api-info {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.profile-section .api_key
|
||||
.profile-section #api_id_key,
|
||||
.profile-section #api_secret_key {
|
||||
font-family: monospace !important;
|
||||
}
|
||||
|
||||
/* Modal */
|
||||
|
||||
.bg-modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
z-index: 100;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
background-color: rgba(0,0,0,0.7);
|
||||
}
|
||||
|
||||
.close-modal {
|
||||
position: absolute;
|
||||
right: -40px;
|
||||
top: -40px;
|
||||
font-size: 30px;
|
||||
text-decoration: none !important;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.body-modal {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: absolute;
|
||||
border-radius: 10px;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -48%);
|
||||
text-align: left;
|
||||
padding: 30px;
|
||||
max-width: 600px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#api_modal_revoke .body-modal {
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.body-modal .btn-main {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.title-modal {
|
||||
font-size: 24px !important;
|
||||
font-weight: 600 !important;
|
||||
}
|
||||
|
||||
.subtitle-modal {
|
||||
font-size: 16px !important;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.head-modal {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: start;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
#generate_keyapi .head-modal,
|
||||
#api_modal_edit .head-modal,
|
||||
#api_modal_revoke .head-modal {
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.input-modal-id {
|
||||
margin-top: 25px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.input-modal-key {
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
/* The switch */
|
||||
.switch {
|
||||
position: relative;
|
||||
@ -547,6 +844,20 @@ input:checked + .slider:before, input:checked + .slider::after {
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
/* Tooltips */
|
||||
[data-tooltip]:hover::after {
|
||||
position: absolute;
|
||||
content: attr(data-tooltip);
|
||||
animation: fadeIn 100ms cubic-bezier(0.42, 0, 0.62, 1.32) forwards;
|
||||
animation-delay: 100ms;
|
||||
border-radius: 5px;
|
||||
max-width: 100%;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
padding: 5px 10px;
|
||||
box-shadow: 0px 10px 33px #3333332e;
|
||||
}
|
||||
|
||||
/* Light */
|
||||
#theIdentificationPage .light,
|
||||
#theRegisterPage .light,
|
||||
@ -560,16 +871,19 @@ input:checked + .slider:before, input:checked + .slider::after {
|
||||
.light #password-form,
|
||||
.light #lang-select #other-languages,
|
||||
.light .profile-section,
|
||||
.light .slider:before {
|
||||
.light .slider:before,
|
||||
.light .body-modal,
|
||||
.light [data-tooltip]:hover::after {
|
||||
background-color:#ffffff;
|
||||
}
|
||||
|
||||
#theIdentificationPage .light a,
|
||||
#theRegisterPage .light a,
|
||||
#thePasswordPage .light a,
|
||||
#theProfilePage .light a,
|
||||
#theProfilePage .light a:not(.close-modal),
|
||||
.light h1,
|
||||
.light .input-container input,
|
||||
.light .input-modal input,
|
||||
.light .input-container select,
|
||||
.light .input-container textarea,
|
||||
.light .secondary-links,
|
||||
@ -579,7 +893,9 @@ input:checked + .slider:before, input:checked + .slider::after {
|
||||
.light .profile-section i,
|
||||
.light #password-form p,
|
||||
.light .profile-section p,
|
||||
.light #lang-select #other-languages span{
|
||||
.light #lang-select #other-languages span,
|
||||
.light .btn-cancel,
|
||||
.light .btn-link {
|
||||
color:#3C3C3C;
|
||||
}
|
||||
|
||||
@ -596,7 +912,9 @@ input:checked + .slider:before, input:checked + .slider::after {
|
||||
color:#ff7700;
|
||||
}
|
||||
|
||||
.light .input-container{
|
||||
.light .input-container,
|
||||
.light .input-modal,
|
||||
.light .api-list-head {
|
||||
background-color:#F0F0F0;
|
||||
border:1px solid #F0F0F0;
|
||||
}
|
||||
@ -619,7 +937,7 @@ input:checked + .slider:before, input:checked + .slider::after {
|
||||
|
||||
.light .success-message{
|
||||
background-color: #DBF6D7;
|
||||
color: #6DCE5E;
|
||||
color: #6DCE5E !important;
|
||||
border-left: 4px solid #6DCE5E;
|
||||
}
|
||||
|
||||
@ -635,6 +953,35 @@ input:checked + .slider:before, input:checked + .slider::after {
|
||||
background-color: #CCCCCC;
|
||||
}
|
||||
|
||||
.light .api-list-body > div:nth-child(4n+1),
|
||||
.light .api-list-body > div:nth-child(4n+2) {
|
||||
background-color: #F8F8F8;
|
||||
}
|
||||
|
||||
.light .api-tab-line p,
|
||||
.light .api-icon-action i,
|
||||
.light .keys p:not(.api-copy),
|
||||
.light .keys i,
|
||||
.light #api_key_list_expired .api-skull {
|
||||
color: #656565;
|
||||
}
|
||||
|
||||
.light .close-modal {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.light input[type="date"] {
|
||||
color-scheme: light;
|
||||
}
|
||||
|
||||
.light input[type="date"]::-webkit-calendar-picker-indicator {
|
||||
filter: invert(0);
|
||||
}
|
||||
|
||||
.light .btn-revoked {
|
||||
background-color: #EB3223 !important;
|
||||
}
|
||||
|
||||
/* Dark */
|
||||
#theIdentificationPage .dark,
|
||||
#theRegisterPage .dark,
|
||||
@ -646,16 +993,18 @@ input:checked + .slider:before, input:checked + .slider::after {
|
||||
.dark #login-form,
|
||||
.dark #register-form,
|
||||
.dark #password-form,
|
||||
.dark .profile-section{
|
||||
.dark .profile-section,
|
||||
.dark .body-modal {
|
||||
background-color:#3C3C3C;
|
||||
}
|
||||
|
||||
#theIdentificationPage .dark a,
|
||||
#theRegisterPage .dark a,
|
||||
#thePasswordPage .dark a,
|
||||
#theProfilePage .dark a,
|
||||
#theProfilePage .dark a:not(.close-modal),
|
||||
.dark h1,
|
||||
.dark .input-container input,
|
||||
.dark .input-modal input,
|
||||
.dark .input-container select,
|
||||
.dark .input-container textarea,
|
||||
.dark .secondary-links,
|
||||
@ -665,7 +1014,9 @@ input:checked + .slider:before, input:checked + .slider::after {
|
||||
.dark .profile-section i,
|
||||
.dark #password-form p,
|
||||
.dark .profile-section p,
|
||||
.dark #lang-select #other-languages span{
|
||||
.dark #lang-select #other-languages span,
|
||||
.dark .btn-cancel,
|
||||
.dark .btn-link {
|
||||
color:#D6D6D6;
|
||||
}
|
||||
|
||||
@ -683,7 +1034,8 @@ input:checked + .slider:before, input:checked + .slider::after {
|
||||
color:#FFEBD0;
|
||||
}
|
||||
|
||||
.dark .input-container{
|
||||
.dark .input-container,
|
||||
.dark .input-modal {
|
||||
background-color:#303030;
|
||||
border:1px solid #303030;
|
||||
}
|
||||
@ -712,7 +1064,7 @@ input:checked + .slider:before, input:checked + .slider::after {
|
||||
|
||||
.dark .success-message{
|
||||
background-color: #4EA590;
|
||||
color: #AAF6E4;
|
||||
color: #AAF6E4 !important;
|
||||
border-left: 4px solid #AAF6E4;
|
||||
}
|
||||
|
||||
@ -725,14 +1077,40 @@ input:checked + .slider:before, input:checked + .slider::after {
|
||||
background-color: #FFA646;
|
||||
}
|
||||
|
||||
.dark input:focus + .slider {
|
||||
box-shadow: 0 0 1px #FFA646;
|
||||
}
|
||||
|
||||
.dark .slider:before {
|
||||
background-color: #777777;
|
||||
}
|
||||
|
||||
.dark .api-list-head,
|
||||
.dark [data-tooltip]:hover::after{
|
||||
background-color: #2A2A2A;
|
||||
}
|
||||
|
||||
.dark .api-list-body > div:nth-child(4n+1),
|
||||
.dark .api-list-body > div:nth-child(4n+2) {
|
||||
background-color: #333333;
|
||||
}
|
||||
|
||||
.dark .icon-collapse {
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.dark .close-modal {
|
||||
color: #3C3C3C;
|
||||
}
|
||||
|
||||
.dark input[type="date"] {
|
||||
color-scheme: dark;
|
||||
}
|
||||
|
||||
.dark input[type="date"]::-webkit-calendar-picker-indicator {
|
||||
filter: invert(0);
|
||||
}
|
||||
|
||||
.dark .btn-revoked {
|
||||
background-color: #BE4949 !important;
|
||||
}
|
||||
|
||||
/*Responsive display*/
|
||||
@media (max-width: 768px) {
|
||||
#login-form,
|
||||
|
19
tools/ws.htm
19
tools/ws.htm
@ -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 © 2002-2021 <a href="http://piwigo.org">Piwigo Team</a>
|
||||
Copyright © 2002-2025 <a href="http://piwigo.org">Piwigo Team</a>
|
||||
</div> <!-- the_footer -->
|
||||
</div>
|
||||
|
||||
|
@ -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;
|
||||
} */
|
||||
|
103
tools/ws/ws.js
103
tools/ws/ws.js
@ -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();
|
||||
|
||||
}
|
52
ws.php
52
ws.php
@ -1567,6 +1567,58 @@ enabled_high, registration_date, registration_date_string, registration_date_sin
|
||||
$ws_functions_root . 'pwg.users.php',
|
||||
array('admin_only'=>true, 'post_only'=>true)
|
||||
);
|
||||
|
||||
$service->addMethod(
|
||||
'pwg.users.api_key.create',
|
||||
'ws_create_api_key',
|
||||
array(
|
||||
'key_name' => array(),
|
||||
'duration' => array(
|
||||
'type' => WS_TYPE_INT|WS_TYPE_POSITIVE,
|
||||
'info' => 'Number of days',
|
||||
),
|
||||
'pwg_token' => array(),
|
||||
),
|
||||
'Create a new api key for the user in the current session',
|
||||
$ws_functions_root . 'pwg.users.php',
|
||||
array('admin_only'=>false, 'post_only'=>true)
|
||||
);
|
||||
|
||||
$service->addMethod(
|
||||
'pwg.users.api_key.revoke',
|
||||
'ws_revoke_api_key',
|
||||
array(
|
||||
'pkid' => array(),
|
||||
'pwg_token' => array(),
|
||||
),
|
||||
'Revoke a api key for the user in the current session',
|
||||
$ws_functions_root . 'pwg.users.php',
|
||||
array('admin_only'=>false, 'post_only'=>true)
|
||||
);
|
||||
|
||||
$service->addMethod(
|
||||
'pwg.users.api_key.edit',
|
||||
'ws_edit_api_key',
|
||||
array(
|
||||
'key_name' => array(),
|
||||
'pkid' => array(),
|
||||
'pwg_token' => array(),
|
||||
),
|
||||
'Edit a api key for the user in the current session',
|
||||
$ws_functions_root . 'pwg.users.php',
|
||||
array('admin_only'=>false, 'post_only'=>true)
|
||||
);
|
||||
|
||||
$service->addMethod(
|
||||
'pwg.users.api_key.get',
|
||||
'ws_get_api_key',
|
||||
array(
|
||||
'pwg_token' => array(),
|
||||
),
|
||||
'Get all api key for the user in the current session',
|
||||
$ws_functions_root . 'pwg.users.php',
|
||||
array('admin_only'=>false, 'post_only'=>true)
|
||||
);
|
||||
}
|
||||
|
||||
?>
|
||||
|
Loading…
x
Reference in New Issue
Block a user