Add LinkedIn OAuth connect with portal-saved app credentials.
Replace manual token paste with Authorization Code flow; store Client ID/secret in the DB from the social accounts UI and surface a copyable callback URL for LinkedIn Auth setup.
This commit is contained in:
@@ -27,7 +27,7 @@
|
||||
<span class="platform-icon li">in</span>
|
||||
<div>
|
||||
<strong>LinkedIn</strong>
|
||||
<p>Personal or organization page. Tokens expire — reconnect when prompted.</p>
|
||||
<p>Connect via OAuth. Tokens expire (~2 months) — re-authorize when prompted.</p>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
@@ -43,6 +43,55 @@
|
||||
<li>Reference: <a href="{{ connect_meta.docs_url }}" target="_blank" rel="noopener">platform docs</a></li>
|
||||
{% endif %}
|
||||
</ol>
|
||||
|
||||
{% if connect_meta.oauth and connect_platform == 'linkedin' %}
|
||||
<div class="field" style="margin:12px 0 16px;padding:12px;border:1px dashed #cbd5e1;border-radius:8px;background:#f8fafc">
|
||||
<label for="linkedin-callback-url" style="display:block;margin-bottom:6px">
|
||||
Authorized redirect URL — paste into LinkedIn Auth tab
|
||||
</label>
|
||||
<div style="display:flex;gap:8px;flex-wrap:wrap;align-items:stretch">
|
||||
<input id="linkedin-callback-url" type="text" readonly
|
||||
value="{{ linkedin_callback_url }}"
|
||||
onclick="this.select()"
|
||||
style="flex:1;min-width:220px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:13px">
|
||||
<button type="button" class="btn btn-ghost btn-sm" id="copy-linkedin-callback">Copy</button>
|
||||
</div>
|
||||
<p class="muted" style="margin:8px 0 0;font-size:13px">
|
||||
Must match exactly (including http/https and trailing slash).
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<form method="post" class="form-grid" style="margin-bottom:16px">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="save_linkedin_app">
|
||||
<div class="field">
|
||||
<label for="id_client_id">Client ID</label>
|
||||
<input id="id_client_id" name="client_id" required
|
||||
value="{{ linkedin_client_id }}"
|
||||
placeholder="From LinkedIn Auth tab"
|
||||
autocomplete="off">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="id_client_secret">Primary Client Secret</label>
|
||||
<input id="id_client_secret" name="client_secret" type="password"
|
||||
{% if not linkedin_secret_saved %}required{% endif %}
|
||||
placeholder="{% if linkedin_secret_saved %}Leave blank to keep saved secret{% else %}From LinkedIn Auth tab{% endif %}"
|
||||
autocomplete="new-password">
|
||||
{% if linkedin_secret_saved %}
|
||||
<p class="muted" style="margin:6px 0 0;font-size:13px">Secret already saved (encrypted). Enter a new value only to replace it.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div style="display:flex;gap:8px;flex-wrap:wrap">
|
||||
<button class="btn btn-ghost" type="submit">Save app credentials</button>
|
||||
{% if linkedin_oauth_ready %}
|
||||
<a class="btn btn-primary" href="{% url 'social:linkedin_oauth_start' %}">Connect with LinkedIn</a>
|
||||
{% else %}
|
||||
<button class="btn btn-primary" type="button" disabled title="Save Client ID and Client Secret first">Connect with LinkedIn</button>
|
||||
{% endif %}
|
||||
<a class="btn btn-ghost" href="{% url 'social:account_list' %}">Cancel</a>
|
||||
</div>
|
||||
</form>
|
||||
{% else %}
|
||||
<form method="post" class="form-grid">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="connect">
|
||||
@@ -65,6 +114,7 @@
|
||||
<a class="btn btn-ghost" href="{% url 'social:account_list' %}">Cancel</a>
|
||||
</div>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
@@ -103,7 +153,11 @@
|
||||
</td>
|
||||
<td>
|
||||
<div style="display:flex;gap:8px;flex-wrap:wrap">
|
||||
{% if account.platform == 'linkedin' %}
|
||||
<a class="btn btn-ghost btn-sm" href="{% url 'social:linkedin_oauth_start' %}">Re-authorize</a>
|
||||
{% else %}
|
||||
<a class="btn btn-ghost btn-sm" href="{% url 'social:account_list' %}?connect={{ account.platform }}">Update tokens</a>
|
||||
{% endif %}
|
||||
{% if account.is_active %}
|
||||
<form method="post" style="display:inline">
|
||||
{% csrf_token %}
|
||||
@@ -155,4 +209,25 @@
|
||||
document.getElementById('connect-form')?.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
</script>
|
||||
{% endif %}
|
||||
{% if connect_platform == 'linkedin' %}
|
||||
<script>
|
||||
(function () {
|
||||
const btn = document.getElementById('copy-linkedin-callback');
|
||||
const input = document.getElementById('linkedin-callback-url');
|
||||
if (!btn || !input) return;
|
||||
btn.addEventListener('click', async () => {
|
||||
const value = input.value || '';
|
||||
try {
|
||||
await navigator.clipboard.writeText(value);
|
||||
const prev = btn.textContent;
|
||||
btn.textContent = 'Copied';
|
||||
setTimeout(() => { btn.textContent = prev; }, 1500);
|
||||
} catch (_err) {
|
||||
input.select();
|
||||
document.execCommand('copy');
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user