Skip to content

Commit 52d5511

Browse files
Add API key
1 parent e2fd3ba commit 52d5511

File tree

18 files changed

+571
-5
lines changed

18 files changed

+571
-5
lines changed

public/css/api-settings.css

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

public/js/api-settings.js

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

resources/js/api-settings.js

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
$(() => {
2+
'use strict';
3+
4+
// Edit API key - enable the field for editing
5+
$(document).on('click', '#edit-api-key', function(e) {
6+
e.preventDefault();
7+
8+
const apiKeyInput = $('#api-key-input');
9+
const editButton = $(this);
10+
const copyButton = $('#copy-api-key');
11+
12+
// Enable the input field
13+
apiKeyInput.prop('readonly', false).focus();
14+
15+
// Hide edit button and show copy button
16+
editButton.hide();
17+
copyButton.show();
18+
19+
// Show success message
20+
Botble.showSuccess(window.trans.api.api_key_edit_enabled || 'API key field is now editable.');
21+
});
22+
23+
// Generate random API key
24+
$('#generate-api-key').on('click', function(e) {
25+
e.preventDefault();
26+
27+
const apiKeyInput = $('#api-key-input');
28+
const editButton = $('#edit-api-key');
29+
const copyButton = $('#copy-api-key');
30+
const newApiKey = generateRandomApiKey();
31+
32+
// Enable the input field and set new value
33+
apiKeyInput.prop('readonly', false).val(newApiKey);
34+
35+
// Update button visibility
36+
editButton.hide();
37+
copyButton.show();
38+
39+
// Show success message
40+
Botble.showSuccess(window.trans.api.api_key_generated || 'API key generated successfully!');
41+
42+
// Update examples with new key
43+
updateExamplesWithApiKey(newApiKey);
44+
});
45+
46+
// The copy functionality is now handled by the <x-core::copy> component
47+
48+
// Update examples when API key changes
49+
$('#api-key-input').on('input', function() {
50+
const apiKey = $(this).val() || 'your-api-key-here';
51+
updateExamplesWithApiKey(apiKey);
52+
53+
// Show/hide copy button based on whether there's a value
54+
const copyButton = $('#copy-api-key');
55+
if (apiKey && apiKey !== 'your-api-key-here') {
56+
copyButton.show();
57+
} else {
58+
copyButton.hide();
59+
}
60+
});
61+
62+
// Initialize examples on page load
63+
const currentApiKey = $('#api-key-input').val() || 'your-api-key-here';
64+
updateExamplesWithApiKey(currentApiKey);
65+
66+
/**
67+
* Generate a random API key
68+
* @returns {string}
69+
*/
70+
function generateRandomApiKey() {
71+
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
72+
let result = '';
73+
74+
// Generate a 32-character random string
75+
for (let i = 0; i < 32; i++) {
76+
result += chars.charAt(Math.floor(Math.random() * chars.length));
77+
}
78+
79+
return result;
80+
}
81+
82+
/**
83+
* Update code examples with the current API key
84+
* @param {string} apiKey
85+
*/
86+
function updateExamplesWithApiKey(apiKey) {
87+
const baseUrl = window.location.origin + '/api/v1';
88+
89+
// Update cURL example
90+
const curlExample = `curl -X GET "${baseUrl}/products" \\
91+
-H "Accept: application/json" \\
92+
-H "X-API-KEY: ${apiKey}"`;
93+
94+
$('#curl-example').text(curlExample);
95+
96+
// Update JavaScript example
97+
const jsExample = `fetch("${baseUrl}/products", {
98+
method: "GET",
99+
headers: {
100+
"Accept": "application/json",
101+
"X-API-KEY": "${apiKey}"
102+
}
103+
})
104+
.then(response => response.json())
105+
.then(data => console.log(data));`;
106+
107+
$('#js-example').text(jsExample);
108+
}
109+
});

resources/lang/en/api.php

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,25 @@
55
'settings_description' => 'View and update your API settings',
66
'save_settings' => 'Save settings',
77
'setting_title' => 'API settings',
8-
'setting_description' => 'Settings for API',
9-
'api_enabled' => 'API enabled?',
8+
'setting_description' => 'Configure your API access and security settings',
9+
'api_enabled' => 'Enable API',
10+
'api_enabled_description' => 'Enable or disable the REST API for your website. When disabled, all API endpoints will be inaccessible.',
11+
'api_key' => 'API Key',
12+
'api_key_description' => 'Optional security key for API access. When set, all API requests must include this key in the X-API-KEY header.',
13+
'api_key_placeholder' => 'Enter your API key (leave empty to disable)',
14+
'generate_api_key' => 'Generate Random Key',
15+
'edit_api_key' => 'Edit API Key',
16+
'copy_api_key' => 'Copy to Clipboard',
17+
'api_key_generated' => 'New API key generated successfully!',
18+
'api_key_copied' => 'API key copied to clipboard!',
19+
'api_key_edit_enabled' => 'API key field is now editable.',
20+
'api_documentation' => 'API Documentation',
21+
'api_documentation_description' => 'View the complete API documentation with examples and endpoint details.',
22+
'view_documentation' => 'View API Docs',
23+
'api_usage_examples' => 'Usage Examples',
24+
'api_usage_curl_example' => 'Example cURL request with API key:',
25+
'api_usage_javascript_example' => 'Example JavaScript request:',
26+
'api_security_section' => 'Security Settings',
27+
'api_general_section' => 'General Settings',
28+
'api_help_section' => 'Help & Documentation',
1029
];

resources/sass/api-settings.scss

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
// API Settings Styles
2+
3+
.api-key-field {
4+
.btn-group {
5+
.btn {
6+
border-radius: 0.375rem;
7+
8+
&:first-child {
9+
border-top-right-radius: 0;
10+
border-bottom-right-radius: 0;
11+
}
12+
13+
&:last-child {
14+
border-top-left-radius: 0;
15+
border-bottom-left-radius: 0;
16+
border-left: 0;
17+
}
18+
19+
&:hover {
20+
z-index: 2;
21+
}
22+
}
23+
}
24+
25+
.form-control {
26+
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace;
27+
font-size: 0.875rem;
28+
29+
&[readonly] {
30+
background-color: #f8f9fa;
31+
border-color: #dee2e6;
32+
cursor: not-allowed;
33+
34+
&:focus {
35+
background-color: #f8f9fa;
36+
border-color: #dee2e6;
37+
box-shadow: none;
38+
}
39+
}
40+
}
41+
}
42+
43+
.api-documentation-card {
44+
transition: all 0.2s ease-in-out;
45+
46+
&:hover {
47+
transform: translateY(-2px);
48+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
49+
}
50+
}
51+
52+
.api-code-example {
53+
position: relative;
54+
55+
pre {
56+
border-radius: 0.5rem;
57+
margin-bottom: 0;
58+
59+
code {
60+
font-size: 0.8rem;
61+
line-height: 1.4;
62+
}
63+
}
64+
65+
.copy-button {
66+
opacity: 0.7;
67+
transition: opacity 0.2s ease-in-out;
68+
69+
&:hover {
70+
opacity: 1;
71+
}
72+
}
73+
}
74+
75+
// Responsive improvements
76+
@media (max-width: 576px) {
77+
.api-key-field {
78+
.row {
79+
.col-auto {
80+
.btn-group {
81+
width: 100%;
82+
margin-top: 0.5rem;
83+
84+
.btn {
85+
flex: 1;
86+
border-radius: 0.375rem !important;
87+
border-left: 1px solid var(--bs-border-color) !important;
88+
89+
&:first-child {
90+
border-top-right-radius: 0.375rem !important;
91+
border-bottom-right-radius: 0.375rem !important;
92+
}
93+
94+
&:last-child {
95+
border-top-left-radius: 0.375rem !important;
96+
border-bottom-left-radius: 0.375rem !important;
97+
}
98+
}
99+
}
100+
}
101+
}
102+
}
103+
104+
.api-code-example {
105+
pre {
106+
padding: 1rem !important;
107+
font-size: 0.75rem;
108+
}
109+
}
110+
}

resources/views/settings.blade.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,12 @@
1111
</div>
1212
@endif
1313
@endsection
14+
15+
@push('footer')
16+
<script>
17+
'use strict';
18+
19+
window.trans = window.trans || {};
20+
window.trans.api = {!! json_encode($translations, JSON_HEX_APOS) !!};
21+
</script>
22+
@endpush
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
@php
2+
$hasApiKey = !empty($apiKey ?? '');
3+
@endphp
4+
5+
<div class="btn-group" role="group">
6+
<button type="button" class="btn btn-outline-primary" id="generate-api-key" title="{{ trans('packages/api::api.generate_api_key') }}">
7+
<x-core::icon name="ti ti-refresh" />
8+
<span class="d-none d-sm-inline ms-1">{{ trans('packages/api::api.generate_api_key') }}</span>
9+
</button>
10+
11+
<x-core::copy
12+
:copyableState="''"
13+
:copyableMessage="trans('packages/api::api.api_key_copied')"
14+
class="btn btn-outline-secondary {{ !$hasApiKey ? 'd-none' : '' }}"
15+
title="{{ trans('packages/api::api.copy_api_key') }}"
16+
id="copy-api-key"
17+
data-clipboard-target="#api-key-input"
18+
>
19+
<x-core::icon name="ti ti-clipboard" class="me-0" />
20+
<span class="d-none d-sm-inline ms-1">{{ trans('packages/api::api.copy_api_key') }}</span>
21+
</x-core::copy>
22+
</div>
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<div class="mb-3 api-key-field">
2+
<label for="api-key-input" class="form-label">
3+
{{ trans('packages/api::api.api_key') }}
4+
</label>
5+
6+
<div class="row g-2">
7+
<div class="col">
8+
<input
9+
type="text"
10+
class="form-control"
11+
id="api-key-input"
12+
name="api_key"
13+
value="{{ $apiKey }}"
14+
placeholder="{{ trans('packages/api::api.api_key_placeholder') }}"
15+
autocomplete="off"
16+
@if(!empty($apiKey)) readonly @endif
17+
>
18+
</div>
19+
<div class="col-auto">
20+
@include('packages/api::settings.partials.api-key-actions', ['apiKey' => $apiKey])
21+
</div>
22+
</div>
23+
24+
<div class="form-text">
25+
{{ trans('packages/api::api.api_key_description') }}
26+
</div>
27+
28+
@if(!empty($apiKey))
29+
<div class="mt-2">
30+
<small class="text-success">
31+
<x-core::icon name="ti ti-shield-check" class="me-1" />
32+
API key protection is <strong>enabled</strong>. All requests require the X-API-KEY header.
33+
</small>
34+
</div>
35+
@else
36+
<div class="mt-2">
37+
<small class="text-warning">
38+
<x-core::icon name="ti ti-shield-x" class="me-1" />
39+
API key protection is <strong>disabled</strong>. Endpoints are publicly accessible.
40+
</small>
41+
</div>
42+
@endif
43+
</div>
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
@php
2+
$docsUrl = url('/docs');
3+
@endphp
4+
5+
<div class="card border-0 bg-white api-documentation-card">
6+
<div class="card-body">
7+
<div class="d-flex align-items-center">
8+
<div class="flex-grow-1">
9+
<h6 class="mb-1">{{ trans('packages/api::api.api_documentation') }}</h6>
10+
<p class="mb-2 text-muted">{{ trans('packages/api::api.api_documentation_description') }}</p>
11+
<a href="{{ $docsUrl }}" target="_blank" class="btn btn-sm btn-primary">
12+
<x-core::icon name="ti ti-external-link" class="me-1" />
13+
{{ trans('packages/api::api.view_documentation') }}
14+
</a>
15+
</div>
16+
</div>
17+
</div>
18+
</div>
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
@php
2+
$baseUrl = url('/api/v1');
3+
@endphp
4+
5+
<div class="mt-3">
6+
<h6>{{ trans('packages/api::api.api_usage_examples') }}</h6>
7+
8+
<div class="mb-3 api-code-example">
9+
<label class="form-label">{{ trans('packages/api::api.api_usage_curl_example') }}</label>
10+
<div class="position-relative">
11+
<pre class="bg-dark text-light p-3 rounded" style="font-size: 0.875rem;"><code id="curl-example">curl -X GET "{{ $baseUrl }}/products" \
12+
-H "Accept: application/json" \
13+
-H "X-API-KEY: your-api-key-here"</code></pre>
14+
<x-core::copy
15+
:copyableState="''"
16+
copyableMessage="Code copied to clipboard!"
17+
class="btn btn-sm btn-outline-light position-absolute top-0 end-0 m-2 copy-button"
18+
data-clipboard-target="#curl-example"
19+
/>
20+
</div>
21+
</div>
22+
23+
<div class="mb-3 api-code-example">
24+
<label class="form-label">{{ trans('packages/api::api.api_usage_javascript_example') }}</label>
25+
<div class="position-relative">
26+
<pre class="bg-dark text-light p-3 rounded" style="font-size: 0.875rem;"><code id="js-example">fetch("{{ $baseUrl }}/products", {
27+
method: "GET",
28+
headers: {
29+
"Accept": "application/json",
30+
"X-API-KEY": "your-api-key-here"
31+
}
32+
})
33+
.then(response => response.json())
34+
.then(data => console.log(data));</code></pre>
35+
<x-core::copy
36+
:copyableState="''"
37+
copyableMessage="Code copied to clipboard!"
38+
class="btn btn-sm btn-outline-light position-absolute top-0 end-0 m-2 copy-button"
39+
data-clipboard-target="#js-example"
40+
/>
41+
</div>
42+
</div>
43+
</div>

0 commit comments

Comments
 (0)