PageSourceSearch

https://catholiclibrary.org/clauth/manage_account

html catholiclibrary.org collected 2026-10-02 04:43:50 UTC 17,605 bytes, 362 lines download raw bytes

1<!DOCTYPE html>
2<html lang="en">
3  <head>
4	<meta charset="utf-8">
5    <title>Account management &ndash; CatholicLibrary.org</title>
6	<meta name="viewport" content="width=device-width, initial-scale=1" />
7	<link rel="stylesheet" type="text/css" href="/assets/css/library.css"></link>
8	<link rel="shortcut icon" type="image/x-icon" href="/favicon.ico" />
9	
9<script src="/assets/js/auth.js"></script>
9
10	
10<script defer src="/assets/js/library.js?v=4"></script>
10
11  </head>
12  <body>
13  <img id="libimg" alt="Catholic Library Home" src="/images/cathlib2.png" style="height:44px;margin:0; position:fixed; top:0; left:0;" />
14  <header id="header" class="header">
15<nav>
16<ul>
17	<li><a href="/library/search">Search</a></li>
18	<li><span id='browse'>Browse</span>
19	  <ul>
20		<li><a href="/library/browse">Centuries</a></li>
21		<li><a href="/library/browse/name/a">Authors</a></li>
22		<li><a href="/library/search?&sort=auth-year&smode=universal&label=doctor">Doctors</a></li>
23		<li><a href="/library/search?&sort=auth-year&smode=universal&label=father">Fathers</a></li>
24		<li><a href="/library/search?&sort=auth-year&smode=universal&label=pucmagisterium">Magisterium</a></li>
25		<li><a href="/library/search?&sort=auth-year&smode=universal&label=reftext">Reference</a></li>
26		</ul>
27	</li>
28	<li><a href="/bible/search">Scripture</a></li>
29	<li><a href="/art">Art</a></li>
30	<li><span class='loggedout'>Register / Login</span><span class='loggedin'>Account</span>
31	  <ul>
32	  	<li id="nav_login"  class='loggedout'><a href="/clauth/login">Login</a></li>
33		<li><a href="/userprefs.php">Preferences</a></li>
34		<li><a href="/notes.php">Notes</a></li>
35		<li  class='loggedout'><a href="/clauth/registration">Register</a></li>
36		<li class='loggedin'><a href="/clauth/manage_account">Account management</a></li>
37		<li class='loggedin'><a href="/clauth/logout">Log out</a></li>
38		<li  class='loggedout'><a href="/institutions">Catholic Organizations</a></li>
39		</ul>
40	</li>
41	<li><a href="/institutions/sponsor.php">Sponsor us</a></li>
42	<li><span>...</span>
43	  <ul>
44		<li><a href="/prayers/">Prayers and Devotions</a></li>
45<li><a href="/about/Catholic_Library">About CatholicLibrary.org</a></li>
46		<li><a href="/about/Catholic_Library/updates.php">Latest updates</a></li>
47		<li><a href="/about/Prof_Seth_Cherney">About Seth Cherney</a></li>
48		<li><a href="/help/">Site Help</a></li>
49		<li><a href="/terms/">Terms</a></li>
50		<li><a href="/privacy/">Privacy</a></li>
51	  <!-- <li><a href="/institutions/parishes.php">Parishes</a></li>
52		<li><a href="/institutions/libraries.php">Libraries / MARC</a></li>
53		<li><a href="/institutions/universities.php">Universities</a></li>
54		<li><a href="/institutions/schools.php">Schools</a></li> -->
55		</ul>
56	</li>
57</ul>
58</nav>
59</header>    <h1>Account Management</h1>
60    <div id="status-msg"></div>
61
62    <div id="reset-password-form" class="card" style="display:none;">
63        <h3>Set New Password</h3>
64        <input type="password" id="new_password" placeholder="New Password">
65        <button onclick="completeReset()" class="btn">Update Password</button>
66    </div>
67
68    <div id="loading">Loading...</div>
69
70    <div id="main-content" style="display:none;">
71        <div class="card">
72            <h3>Security</h3>
73            <p>Update your password by requesting a secure link.</p>
74            <button onclick="handleAction('reset_password_request')" class="btn btn-outline">Send Password Reset Link</button>
75        </div>
76
77        <div id="billing-section" class="card" style="display:none;">
78            <h3>Billing & Subscription</h3>
79            <div id="billing-auth-lock">
80                <p>To access billing, please re-enter your password for security.</p>
81                <input type="password" id="confirm_pass_billing" placeholder="Account Password">
82                <button onclick="getBillingAccess()" class="btn">Confirm & Manage Billing</button>
83            </div>
84            <div id="billing-link-container" style="display:none;">
85                <p>Access granted. You can now manage your subscription on Stripe.</p>
86                <a href="#" id="stripe-portal-btn" class="btn">Open Stripe Billing Portal</a>
87            </div>
88        </div>
89
90        <div class="card">
91            <h3>Site preferences</h3>
92            <button onclick="window.location.href = '/userprefs.php';" class="btn btn-outline">Set your preferences</button>
93        </div>
94
95        <div id="family-section" class="card" style="display:none;">
96            <h3>Family Emails - allows free registration</h3>
97            <p> They will receive an email with instructions to register for free. If you want to register for your kids, use "plus notation" like [email protected] etc, and you will get the email in your inbox. If they are old enough, they will want to do this for themselves so they don't need to worry their notes are not private.</p>
98            <p>It is not strictly immediate family. Friends are fine too!</p>
99            <div id="family-inputs"></div>
100            <button onclick="updateFamily()" class="btn btn-black" style="margin-top:15px;">Save Family List</button>
101        </div>
102        <div id="group-section" class="card" style="display:none;">
103		    <h3>Group Management</h3>
104		    <p style="margin: 0 0 12px 0;">
105		        <strong>Your Group Code:</strong> 
106		        <span id="display-group-code" style="font-family: monospace; padding: 4px 8px; border-radius: 5px; border: 1px solid #ccc; line-height: 24px;"></span>
107		        <button onclick="copyGroupCode(event)" class="btn-outline" style="width: auto; padding: 4px 10px; margin-left: 10px; font-size: 12px; line-height: 24px;  border-radius: 5px; display: inline-block;">Copy</button>
108		    </p>
109		    <strong>Existing Members:</strong>
110		    <ul id="group-list" style="list-style: none; padding: 0;"></ul>
111		    
112		    <hr style="border: 0; border-top: 1px solid #eee; margin: 20px 0;">
113		    
114		    <p style="margin: 0 0 12px 0;"><b>Bulk Add Members</b> (One email per line). New members receive an email with intructions to complete their signup. You don't need to send them the code.</p>
115		    <textarea id="group-bulk-input" rows="5" style="width:100%; border: 1px solid #ddd; border-radius: 4px; padding: 10px;"></textarea>
116		    <button onclick="bulkAddGroup()" class="btn btn-black" style="margin-top:10px;">Add Members</button>
117		</div>
118		<div id="institution-section" class="card" style="display:none;">
119			<h3>Institution Authentication</h3>
120			<p><small>Your users can use the library's advanced features for free by registering with these email domains.</small></p>
121
122			<!-- <div style="margin-bottom: 20px;">
123			    <label><strong>Authorized IP Ranges (CIDR notation, max 5)</strong></label>
124			    <div id="inst-ip-container"></div>
125			</div> -->
126
127			<div style="margin-bottom: 20px;">
128			    <label><strong>Authorized Email Domains</strong></label>
129			    <div id="inst-domain-container"></div>
130			</div>
131
132			<button onclick="updateInstitution()" class="btn btn-black">Save Institution Settings</button>
133		</div>
134        <p><a onclick="logout()" style="color: #8898aa; text-decoration: none; cursor: pointer;">&larr; Logout</a></p>
135    </div><!-- end main content -->
136    <div id="guest-content" style="display:none;">
137        <div class="card">
138            <h3>Patron Access</h3>
139            <button onclick="login()" class="btn">Login</button>
140            <p style="text-align: center;">or</p>
141            <button onclick="window.location.href='/clauth/forgot_password.php';" class="btn">Reset Password</button>
142            <!-- <p style="text-align: center;">or</p>
143            <input type="email" id="lookup_email" placeholder="Email address">
144            <button onclick="handleAction('patron_lookup')" class="btn">Email Stripe Link</button> -->
145            
146        </div>
147    </div>
148    <style>
149        body { font-family: -apple-system, sans-serif; line-height: 1.6; color: #333; max-width: 600px; margin: 40px auto; padding: 20px; background: #f4f7f9; }
150        .card { border: 1px solid #e3e8ee; padding: 30px; border-radius: 8px; background: #fff; box-shadow: 0 2px 5px rgba(0,0,0,0.05); margin-bottom: 20px; }
151        .btn { background: #635bff; color: white; padding: 12px 20px; border-radius: 5px; border: none; font-size: 15px; cursor: pointer; display: inline-block; width: 100%; box-sizing: border-box; font-weight: 600; text-align: center; }
152        .btn-black { background: #24292e; }
153        .btn-outline { background: #fff; color: #635bff; border: 1px solid #635bff; }
154        input { width: 100%; padding: 12px; margin: 8px 0; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box; }
155        .error { color: #d93025; background: #fce8e6; padding: 15px; border-radius: 4px; margin-bottom: 20px; border: 1px solid #f1c4c2; }
156        .success { color: #0d652d; background: #e6f4ea; padding: 15px; border-radius: 4px; margin-bottom: 20px; border: 1px solid #c2e7cd; }
157        label { font-size: 0.85em; font-weight: bold; color: #666; text-transform: uppercase; display: block; margin-top: 10px; }
158        h3 { margin-top: 0; border-bottom: 1px solid #eee; padding-bottom: 10px; margin-bottom: 20px; }
159    </style>
160    
160<script>
161const urlParams = new URLSearchParams(window.location.search);
162//const jwt = sessionStorage.getItem('cl_auth');
163
164// THIS WILL BREAK if using httponly manage_account_handler has been updated so can change this to credientials:include ...
165const jwt = document.cookie.split('; ').find(row => row.startsWith('cl_auth='))?.split('=')[1] || null;
166
167async function init() {
168    if (urlParams.has('selector') && urlParams.has('token')) {
169        document.getElementById('loading').style.display = 'none';
170        document.getElementById('reset-password-form').style.display = 'block';
171        return;
172    }
173
174    try {
175        const res = await fetch('manage_account_handler.php?fetch=1', {
176            credentials: 'include'
177        });
178        const data = await res.json();
179        document.getElementById('loading').style.display = 'none';
180
181        if (data.loggedIn) {
182            document.getElementById('main-content').style.display = 'block';
183            if (!data.isMemberType || data.hasPatronage) {
184                document.getElementById('billing-section').style.display = 'block';
185            }
186            if (data.isFamilyPlan) {
187                document.getElementById('family-section').style.display = 'block';
188                let html = '';
189                for (let i = 0; i < 7; i++) {
190                    html += `<label>Member ${i+1}</label><input type="email" class="fam-email" value="${data.familyMembers[i] || ''}">`;
191                }
192                document.getElementById('family-inputs').innerHTML = html;
193            } else if (data.isGroupPlan) {
194		        document.getElementById('group-section').style.display = 'block';
195		        document.getElementById('display-group-code').innerText = data.groupCode || 'No code assigned';
196		        renderGroupList(data.groupMembers);
197		    }  else if (data.isInstitution) {
198			    document.getElementById('institution-section').style.display = 'block';
199			    // renderInstInputs('inst-ip-container', data.instIps, 'e.g. 192.168.1.0/24');
200			    renderInstInputs('inst-domain-container', data.instDomains, 'e.g. school.edu');
201			}
202        } else {
203            document.getElementById('guest-content').style.display = 'block';
204        }
205    } catch (e) { document.getElementById('loading').innerText = "Error loading page."; }
206}
207
208async function getBillingAccess() {
209    const password = document.getElementById('confirm_pass_billing').value.trim();
210    console.log(password);
211    const res = await fetch('manage_account_handler.php', {
212        method: 'POST',
213        headers: { 'Authorization': `Bearer ${jwt}`, 'Content-Type': 'application/json' },
214        body: JSON.stringify({ action: 'get_billing_access', confirm_password: password })
215    });
216    const result = await res.json();
217    if (result.success) {
218        document.getElementById('billing-auth-lock').style.display = 'none';
219        document.getElementById('billing-link-container').style.display = 'block';
220        document.getElementById('stripe-portal-btn').href = result.url;
221    } else {
222        showStatus(result.message, 'error');
223    }
224}
225
226async function completeReset() {
227    const res = await fetch('manage_account_handler.php', {
228        method: 'POST',
229        headers: { 'Content-Type': 'application/json' },
230        body: JSON.stringify({
231            action: 'complete_password_reset',
232            selector: urlParams.get('selector'),
233            token: urlParams.get('token'),
234            new_password: document.getElementById('new_password').value
235        })
236    });
237    const result = await res.json();
238    showStatus(result.message, result.success ? 'success' : 'error');
239    if (result.success) document.getElementById('reset-password-form').style.display = 'none';
240}
241
242async function updateFamily() {
243    const emails = Array.from(document.querySelectorAll('.fam-email')).map(i => i.value);
244    const res = await fetch('manage_account_handler.php', {
245        method: 'POST',
246        headers: { 'Authorization': `Bearer ${jwt}`, 'Content-Type': 'application/json' },
247        body: JSON.stringify({ action: 'update_family', family_emails: emails })
248    });
249    const result = await res.json();
250    showStatus(result.message, result.success ? 'success' : 'error');
251}
252
253// --- START GROUP FUNCTIONS ---
254function renderGroupList(members) {
255    const list = document.getElementById('group-list');
256    list.innerHTML = members.map(m => `
257        <li style="display:flex; justify-content:space-between; padding: 8px 0; border-bottom: 1px solid #f9f9f9;">
258            ${m} <span onclick="removeMember('${m}')" style="color:red; cursor:pointer; font-weight:bold;">&times;</span>
259        </li>
260    `).join('');
261}
262
263async function bulkAddGroup() {
264    const blob = document.getElementById('group-bulk-input').value;
265    const res = await fetch('manage_account_handler.php', {
266        method: 'POST',
267        headers: { 'Authorization': `Bearer ${jwt}`, 'Content-Type': 'application/json' },
268        body: JSON.stringify({ action: 'bulk_add_group', emails_blob: blob })
269    });
270    const result = await res.json();
271    showStatus(result.message, result.success ? 'success' : 'error');
272    if (result.success) setTimeout(() => location.reload(), 1500);
273}
274
275async function removeMember(email) {
276    if (!confirm(`Remove ${email}?`)) return;
277    const res = await fetch('manage_account_handler.php', {
278        method: 'POST',
279        headers: { 'Authorization': `Bearer ${jwt}`, 'Content-Type': 'application/json' },
280        body: JSON.stringify({ action: 'remove_group_member', member_email: email })
281    });
282    const result = await res.json();
283    if (result.success) location.reload();
284}
285
286async function handleAction(action) {
287    const res = await fetch('manage_account_handler.php', {
288        method: 'POST',
289        headers: { 'Authorization': `Bearer ${jwt}`, 'Content-Type': 'application/json' },
290        body: JSON.stringify({ action, lookup_email: document.getElementById('lookup_email')?.value })
291    });
292    const result = await res.json();
293    showStatus(result.message, result.success ? 'success' : 'error');
294}
295
296function showStatus(msg, type) {
297    document.getElementById('status-msg').innerHTML = `<div class="${type}">${msg}</div>`;
298    window.scrollTo(0,0);
299}
300function login() {
301	window.location.href = "login.php?redirect=" + btoa(window.location.href);
302}
303async function logout() {
304	await fetch('logout.php?mode=json');
305	location.reload();
306}
307function copyGroupCode(e) {
308    const code = document.getElementById('display-group-code').innerText;
309    if (!code || code === 'No code assigned') return;
310
311    navigator.clipboard.writeText(code).then(() => {
312        const btn = e.target;
313        const originalText = btn.innerText;
314        btn.innerText = 'Copied!';
315        btn.style.color = '#0d652d';
316        btn.style.borderColor = '#0d652d';
317        
318        setTimeout(() => {
319            btn.innerText = originalText;
320            btn.style.color = '';
321            btn.style.borderColor = '';
322        }, 2000);
323    }).catch(err => {
324        console.error('Failed to copy: ', err);
325    });
326}
327
328	// --- START INSTITUTION JS ---
329function renderInstInputs(containerId, values, placeholder) {
330    const container = document.getElementById(containerId);
331    container.innerHTML = '';
332    // Ensure we always show at least one blank row, up to 5
333    const rows = [...values];
334    while(rows.length < 5) rows.push('');
335    
336    rows.forEach(val => {
337        const input = document.createElement('input');
338        input.type = 'text';
339        input.value = val;
340        input.placeholder = placeholder;
341        input.className = containerId === 'inst-ip-container' ? 'inst-ip-input' : 'inst-dom-input';
342        input.style.marginBottom = '5px';
343        container.appendChild(input);
344    });
345}
346async function updateInstitution() {
347    const ips = Array.from(document.querySelectorAll('.inst-ip-input')).map(i => i.value);
348    const domains = Array.from(document.querySelectorAll('.inst-dom-input')).map(i => i.value);
349    
350    const res = await fetch('manage_account_handler.php', {
351        method: 'POST',
352        headers: { 'Authorization': `Bearer ${jwt}`, 'Content-Type': 'application/json' },
353        body: JSON.stringify({ action: 'update_institution', ips, domains })
354    });
355    const result = await res.json();
356    showStatus(result.message, result.success ? 'success' : 'error');
357}
358
359        init();
360    </script>
360
361</body>
362</html>

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.