source: Klonkt/src/views/pages/admin-paid.ejs@ 603d246

main
Last change on this file since 603d246 was 603d246, checked in by Robin <roboburr@…>, 7 weeks ago

Feature: show the exact Patreon redirect URI in the paid admin

Bart hit Patreon's own error page ("Redirect URI .../paid/callback is not
supported by client") because the redirect URI our OAuth flow sends was
never whitelisted in his Patreon client, and the admin page told him
nowhere what that URI is. Once we redirect to patreon.com with an
unregistered redirect_uri, Patreon refuses to send the visitor back (open
redirect protection) and shows its own JSON error, which we cannot skin.

The only real defence is correct setup, so the admin now shows the exact
redirect URI to paste into the Patreon client, with a copy button. The URI
is built the same way paid.js builds it (PUBLIC_BASE_URL or the request
host + /paid/callback), so they always match.

Changed files:
src/routes/admin-paid.js

  • compute redirectUri (matches paid.js) and pass it to the view

src/views/pages/admin-paid.ejs

  • "Zet deze redirect-URI in je Patreon-client" block + copy button

-robo
Co-Authored-By: Claude Opus 4.8 <noreply@…>

  • Property mode set to 100644
File size: 4.6 KB
Line 
1<div class="container" style="max-width:640px;margin:1.5rem auto 3rem;padding:0 1rem">
2 <h1 style="margin:0 0 .3rem">Betaalde posts</h1>
3 <p style="color:var(--ink-soft,#888);margin:0 0 1.2rem">
4 Koppel je eigen Patreon-campagne. Supporters ontgrendelen betaalde posts met een passkey,
5 zonder account en zonder cookie. Wij bewaren geen namen of e-mailadressen van supporters,
6 alleen het versleutelde token van jouw campagne.
7 </p>
8
9 <% if (saved) { %><p style="color:var(--accent);font-weight:600">Opgeslagen.</p><% } %>
10 <% if (error) { %><p style="color:#c0392b"><%= error %></p><% } %>
11 <% if (!secretReady) { %>
12 <p style="color:#c0392b">Let op: <code>PAID_SECRET</code> staat niet in de serverconfig. Zonder die sleutel kunnen secrets niet versleuteld worden opgeslagen.</p>
13 <% } %>
14
15 <p style="margin:.2rem 0 1.2rem">
16 Status:
17 <% if (status.connected) { %>
18 <strong style="color:var(--accent)">verbonden</strong> (campagne <%= status.campaignId %>)
19 <% } else if (status.configured) { %>
20 <strong>ingesteld, nog niet verbonden</strong> (vul een token in)
21 <% } else { %>
22 <strong>nog niet ingesteld</strong>
23 <% } %>
24 </p>
25
26 <div style="border:1px solid var(--border,#333);border-radius:10px;padding:.9rem 1rem;margin:0 0 1.3rem;background:color-mix(in srgb,var(--accent,#6b8f71) 8%,transparent)">
27 <p style="margin:0 0 .4rem;font-weight:600">Zet deze redirect-URI in je Patreon-client</p>
28 <p style="margin:0 0 .6rem;color:var(--ink-soft,#888);font-size:.9rem">
29 Bij je Patreon API-client, onder <em>Redirect URIs</em>, moet exact deze regel staan.
30 Klopt hij niet, dan geeft Patreon een foutmelding in plaats van je supporters terug te sturen.
31 </p>
32 <div style="display:flex;gap:.5rem;align-items:center">
33 <input id="pd-redirect" type="text" readonly value="<%= redirectUri %>" style="flex:1;min-width:0;font-family:monospace;font-size:.9rem">
34 <button type="button" id="pd-copy" class="btn">Kopieer</button>
35 </div>
36 </div>
37
38 <form method="post" action="/admin/paid" style="display:flex;flex-direction:column;gap:.9rem">
39 <label>Patreon client id
40 <input type="text" name="client_id" value="<%= status.clientId || '' %>" autocomplete="off" style="width:100%">
41 </label>
42 <label>Patreon client secret
43 <input type="password" name="client_secret" placeholder="<%= status.hasSecret ? 'blijft ongewijzigd' : '' %>" autocomplete="off" style="width:100%">
44 <small style="color:var(--ink-soft,#888)">Leeg laten = huidige waarde behouden.</small>
45 </label>
46 <label>Campagne-id
47 <input type="text" name="campaign_id" value="<%= status.campaignId || '' %>" autocomplete="off" style="width:100%">
48 </label>
49 <label>Creator access token
50 <input type="password" name="access_token" placeholder="blijft ongewijzigd" autocomplete="off" style="width:100%">
51 </label>
52 <label>Creator refresh token
53 <input type="password" name="refresh_token" placeholder="blijft ongewijzigd" autocomplete="off" style="width:100%">
54 <small style="color:var(--ink-soft,#888)">De access + refresh token krijg je op je Patreon API-clientpagina. Wij versleutelen ze en verversen automatisch.</small>
55 </label>
56 <label>Standaard-steunbedrag voor een betaalde post (euro)
57 <input type="text" name="default_min_eur" value="<%= status.defaultMinCents ? (status.defaultMinCents/100).toFixed(2) : '' %>" inputmode="decimal" style="width:120px">
58 </label>
59 <div style="display:flex;gap:.6rem;align-items:center">
60 <button type="submit" class="btn btn-primary">Opslaan</button>
61 </div>
62 </form>
63
64 <% if (status.configured || status.connected) { %>
65 <form method="post" action="/admin/paid/disconnect" data-confirm="Patreon-koppeling verwijderen?" style="margin-top:1rem">
66 <button type="submit" class="btn btn-danger">Koppeling verwijderen</button>
67 </form>
68 <% } %>
69
70 <p style="margin-top:1.5rem"><a href="/admin">&larr; Terug naar Beheer</a></p>
71</div>
72
73<script>
74(function () {
75 var field = document.getElementById('pd-redirect');
76 var btn = document.getElementById('pd-copy');
77 if (!field || !btn) return;
78 field.addEventListener('focus', function () { field.select(); });
79 btn.addEventListener('click', function () {
80 field.select();
81 var done = function () { var t = btn.textContent; btn.textContent = 'Gekopieerd'; setTimeout(function () { btn.textContent = t; }, 1400); };
82 if (navigator.clipboard && navigator.clipboard.writeText) { navigator.clipboard.writeText(field.value).then(done, function () { try { document.execCommand('copy'); done(); } catch (e) {} }); }
83 else { try { document.execCommand('copy'); done(); } catch (e) {} }
84 });
85})();
86</script>
Note: See TracBrowser for help on using the repository browser.