Aniket Mishra commited on
Commit
f29ccf0
·
1 Parent(s): 2ed539c

feat: add 4-digit Passcode PIN login and 1-Click Magic Access Link for seamless editor collaboration

Browse files
Files changed (2) hide show
  1. public/index.html +232 -61
  2. server.js +77 -0
public/index.html CHANGED
@@ -290,15 +290,15 @@
290
  <span>Connect Google</span>
291
  </button>
292
 
293
- <!-- Team Editor Login Button (Email OTP) -->
294
  <button
295
  type="button"
296
  id="navEditorLoginBtn"
297
- onclick="openEditorAuthModal()"
298
- class="flex items-center gap-1.5 px-2.5 h-8 border border-sky-500/50 bg-sky-500/10 hover:bg-sky-500/20 text-sky-400 font-semibold transition-all cursor-pointer text-[11px] uppercase tracking-wider"
299
- title="Log in with authorized Work Email + OTP"
300
  >
301
- <span>🔑 Team Login</span>
302
  </button>
303
 
304
  <!-- Manual Upload Direct Button -->
@@ -1473,10 +1473,51 @@
1473
  <p class="text-[9px] text-[var(--text-muted)]">Controls how many Drive-to-YouTube video streams process in parallel using the SQLite queue.</p>
1474
  </div>
1475
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1476
  <!-- Team & Editor Permissions (Email OTP Access) -->
1477
- <div class="p-3 bg-sky-500/10 border border-sky-500/40 text-xs space-y-2.5">
1478
  <div class="flex items-center justify-between font-bold text-sky-400 uppercase text-[10px]">
1479
- <span>👥 Team &amp; Editor Permissions (Email OTP Access)</span>
1480
  <span class="text-[9px] bg-sky-500/20 text-sky-300 px-1.5 py-0.5 border border-sky-500/30 font-bold" id="teamEditorCount">0 members</span>
1481
  </div>
1482
  <p class="text-[10px] text-[var(--text-muted)]">Give team members/editors access to edit titles, subjects, batches, and update live YouTube thumbnails via Email OTP without needing Google login.</p>
@@ -2002,79 +2043,70 @@
2002
  </div>
2003
  </div>
2004
 
2005
- <!-- 8. Team Editor Email OTP Authentication Modal -->
2006
- <div id="editorAuthModal" class="hidden fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-sm p-4 overflow-y-auto font-mono-code">
2007
- <div class="bg-[var(--bg-card)] border-2 border-[var(--border-main)] max-w-md w-full shadow-2xl my-4 text-[var(--text-main)]">
2008
 
2009
  <!-- Modal Header -->
2010
  <div class="flex items-center justify-between border-b border-[var(--border-main)] px-5 py-3.5 bg-[var(--bg-subcard)]">
2011
  <div class="flex items-center gap-2">
2012
- <span class="text-lg">🔑</span>
2013
  <div>
2014
- <h3 class="text-xs font-bold uppercase tracking-wider text-[var(--text-main)]">Team Editor Access</h3>
2015
- <p class="text-[10px] text-[var(--text-muted)]">Log in with authorized Work Email + 6-digit OTP</p>
2016
  </div>
2017
  </div>
2018
- <button type="button" onclick="closeEditorAuthModal()" class="text-[var(--text-muted)] hover:text-[var(--text-main)] text-xl font-bold cursor-pointer">&times;</button>
2019
  </div>
2020
 
2021
- <!-- Step 1: Request OTP -->
2022
- <div id="editorOtpStep1" class="p-5 space-y-4">
2023
- <div class="space-y-1">
2024
- <label class="text-[10px] font-bold uppercase text-[var(--text-muted)] tracking-wider">Authorized Email Address</label>
 
2025
  <input
2026
- type="email"
2027
- id="editorEmailInput"
2028
- placeholder="e.g. editor@physicswallah.com"
2029
- class="w-full p-2.5 text-xs border border-[var(--border-main)] bg-[var(--bg-card)] text-[var(--text-main)] focus:outline-none focus:border-sky-500"
2030
- onkeydown="if(event.key==='Enter') sendEditorOtp()"
 
 
2031
  />
2032
- <p class="text-[9px] text-[var(--text-muted)] mt-1">Enter the work email authorized by the channel owner in Settings ➔ Team Access.</p>
2033
- </div>
2034
-
2035
- <button
2036
- type="button"
2037
- id="sendOtpBtn"
2038
- onclick="sendEditorOtp()"
2039
- class="w-full py-2.5 bg-sky-600 hover:bg-sky-500 text-white font-bold text-xs uppercase tracking-widest cursor-pointer transition-all shadow-md flex items-center justify-center gap-2"
2040
- >
2041
- <span>SEND 6-DIGIT OTP ➔</span>
2042
- </button>
2043
- </div>
2044
-
2045
- <!-- Step 2: Verify OTP -->
2046
- <div id="editorOtpStep2" class="p-5 space-y-4 hidden">
2047
- <div class="p-3 bg-sky-500/10 border border-sky-500/30 text-xs">
2048
- <div class="flex items-center justify-between text-sky-400 font-bold text-[10px] uppercase">
2049
- <span>OTP Code Sent</span>
2050
- <button type="button" onclick="resetEditorOtpFlow()" class="text-[9px] underline cursor-pointer text-sky-300">Change Email</button>
2051
  </div>
2052
- <p class="text-[10px] text-[var(--text-main)] mt-1" id="editorOtpSentHint">Sent to: user@example.com</p>
2053
  </div>
2054
 
2055
  <div class="space-y-1">
2056
- <label class="text-[10px] font-bold uppercase text-[var(--text-muted)] tracking-wider">Enter 6-Digit OTP Code</label>
2057
  <input
2058
  type="text"
2059
- id="editorOtpInput"
2060
- maxlength="6"
2061
- placeholder="000000"
2062
- class="w-full p-3 text-center tracking-[8px] font-mono font-bold text-xl border-2 border-sky-500/60 bg-[var(--bg-card)] text-[var(--text-main)] focus:outline-none focus:border-sky-400"
2063
- onkeydown="if(event.key==='Enter') verifyEditorOtp()"
2064
  />
2065
  </div>
2066
 
2067
  <button
2068
  type="button"
2069
- id="verifyOtpBtn"
2070
- onclick="verifyEditorOtp()"
2071
- class="w-full py-2.5 bg-emerald-700 hover:bg-emerald-600 text-white font-bold text-xs uppercase tracking-widest cursor-pointer transition-all shadow-md flex items-center justify-center gap-2"
2072
  >
2073
- <span>✔ VERIFY &amp; ENTER STUDIO</span>
2074
  </button>
2075
 
2076
- <div class="text-center">
2077
- <button type="button" onclick="sendEditorOtp()" class="text-[10px] text-[var(--text-muted)] hover:text-sky-400 underline cursor-pointer">Resend OTP Code</button>
2078
  </div>
2079
  </div>
2080
 
@@ -3901,6 +3933,7 @@
3901
 
3902
  loadClientIds();
3903
  loadServerSettings();
 
3904
  loadTeamEditors();
3905
  loadSmtpSettings();
3906
  showAuthLanding();
@@ -4024,7 +4057,7 @@
4024
  navEditorBadge.classList.remove('hidden');
4025
  navEditorBadge.classList.add('flex');
4026
  const label = document.getElementById('navEditorEmailLabel');
4027
- if (label) label.textContent = `Editor (${window.editorEmail || 'Team'})`;
4028
  }
4029
  } else if (isAuth) {
4030
  if (navAuthBtn) { navAuthBtn.classList.add('hidden'); navAuthBtn.classList.remove('flex'); }
@@ -4048,13 +4081,142 @@
4048
  };
4049
 
4050
  // ══════════════════════════════════════════════════════════════════
4051
- // TEAM & EDITOR ACCESS ENGINE (EMAIL + OTP)
4052
  // ══════════════════════════════════════════════════════════════════
4053
 
4054
- window.openEditorAuthModal = function() {
4055
  closeAllModals();
4056
- resetEditorOtpFlow();
4057
- const modal = document.getElementById('editorAuthModal');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4058
  if (modal) modal.classList.remove('hidden');
4059
  const emailInput = document.getElementById('editorEmailInput');
4060
  if (emailInput) {
@@ -7236,7 +7398,16 @@
7236
  // Check for auth callback redirect first
7237
  checkServerAuthOnLoad();
7238
 
7239
- // Auth Priority: 1) Team Editor session (Email OTP), 2) Server-side refresh token, 3) localStorage access token, 4) Silent GIS refresh
 
 
 
 
 
 
 
 
 
7240
  (async () => {
7241
  const isEditor = await checkEditorAuthOnLoad();
7242
  if (isEditor) {
 
290
  <span>Connect Google</span>
291
  </button>
292
 
293
+ <!-- Team Editor PIN Login Button -->
294
  <button
295
  type="button"
296
  id="navEditorLoginBtn"
297
+ onclick="openPinLoginModal()"
298
+ class="flex items-center gap-1.5 px-3 h-8 border border-sky-500/60 bg-sky-500/15 hover:bg-sky-500/25 text-sky-400 font-bold transition-all cursor-pointer text-[11px] uppercase tracking-wider shadow-xs"
299
+ title="Log in with 4-digit Team PIN / Passcode"
300
  >
301
+ <span>🔑 Team PIN Login</span>
302
  </button>
303
 
304
  <!-- Manual Upload Direct Button -->
 
1473
  <p class="text-[9px] text-[var(--text-muted)]">Controls how many Drive-to-YouTube video streams process in parallel using the SQLite queue.</p>
1474
  </div>
1475
 
1476
+ <!-- Team Passcode / PIN & 1-Click Magic Access Link (Primary Collaboration) -->
1477
+ <div class="p-3 bg-sky-500/10 border-2 border-sky-500/60 text-xs space-y-2.5">
1478
+ <div class="flex items-center justify-between font-bold text-sky-400 uppercase text-[10px]">
1479
+ <span>🔑 Team Editor Passcode / PIN (Instant Access)</span>
1480
+ <span class="text-[9px] bg-sky-500/20 text-sky-300 px-1.5 py-0.5 border border-sky-500/30 font-bold font-mono" id="currentPinBadge">PIN: 1234</span>
1481
+ </div>
1482
+ <p class="text-[10px] text-[var(--text-muted)]">Set a 4-digit PIN for your team members. Anyone with this PIN can log in instantly and edit video titles, batches, and live YouTube thumbnails with zero friction.</p>
1483
+
1484
+ <div class="flex gap-2 items-center">
1485
+ <div class="relative flex-1">
1486
+ <input
1487
+ type="text"
1488
+ id="teamPinInput"
1489
+ maxlength="10"
1490
+ placeholder="Set 4-digit PIN (e.g. 1234)..."
1491
+ value="1234"
1492
+ class="w-full p-2 text-xs font-mono font-bold tracking-widest border border-sky-500/40 bg-[var(--bg-card)] text-[var(--text-main)] focus:outline-none focus:border-sky-400"
1493
+ />
1494
+ </div>
1495
+ <button
1496
+ type="button"
1497
+ id="savePinBtn"
1498
+ onclick="saveTeamPin()"
1499
+ class="px-3 py-2 bg-sky-600 hover:bg-sky-500 text-white font-bold text-xs uppercase tracking-wider cursor-pointer whitespace-nowrap"
1500
+ >
1501
+ Save PIN
1502
+ </button>
1503
+ </div>
1504
+
1505
+ <div class="pt-1 flex items-center justify-between bg-[var(--bg-card)] border border-sky-500/30 p-2 text-[10px]">
1506
+ <span class="text-[var(--text-muted)] truncate mr-2">🚀 Share 1-Click Instant Login Link with your editor:</span>
1507
+ <button
1508
+ type="button"
1509
+ onclick="copyMagicEditorLink()"
1510
+ class="px-2.5 py-1 bg-emerald-600 hover:bg-emerald-500 text-white font-bold text-[10px] uppercase cursor-pointer whitespace-nowrap shrink-0 shadow-sm"
1511
+ >
1512
+ 📋 Copy 1-Click Link
1513
+ </button>
1514
+ </div>
1515
+ </div>
1516
+
1517
  <!-- Team & Editor Permissions (Email OTP Access) -->
1518
+ <div class="p-3 bg-sky-500/5 border border-sky-500/30 text-xs space-y-2.5">
1519
  <div class="flex items-center justify-between font-bold text-sky-400 uppercase text-[10px]">
1520
+ <span>👥 Optional: Authorized Email List</span>
1521
  <span class="text-[9px] bg-sky-500/20 text-sky-300 px-1.5 py-0.5 border border-sky-500/30 font-bold" id="teamEditorCount">0 members</span>
1522
  </div>
1523
  <p class="text-[10px] text-[var(--text-muted)]">Give team members/editors access to edit titles, subjects, batches, and update live YouTube thumbnails via Email OTP without needing Google login.</p>
 
2043
  </div>
2044
  </div>
2045
 
2046
+ <!-- 8. Team Editor 4-Digit PIN Authentication Modal -->
2047
+ <div id="pinAuthModal" class="hidden fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-sm p-4 font-mono-code">
2048
+ <div class="bg-[var(--bg-card)] border-2 border-sky-500/60 max-w-sm w-full shadow-2xl my-4 text-[var(--text-main)] overflow-hidden">
2049
 
2050
  <!-- Modal Header -->
2051
  <div class="flex items-center justify-between border-b border-[var(--border-main)] px-5 py-3.5 bg-[var(--bg-subcard)]">
2052
  <div class="flex items-center gap-2">
2053
+ <span class="text-xl">🔑</span>
2054
  <div>
2055
+ <h3 class="text-xs font-bold uppercase tracking-wider text-sky-400">Team Studio Access</h3>
2056
+ <p class="text-[10px] text-[var(--text-muted)]">Enter 4-Digit Passcode / PIN</p>
2057
  </div>
2058
  </div>
2059
+ <button type="button" onclick="closePinLoginModal()" class="text-[var(--text-muted)] hover:text-[var(--text-main)] text-xl font-bold cursor-pointer">&times;</button>
2060
  </div>
2061
 
2062
+ <!-- PIN Input Form -->
2063
+ <div class="p-5 space-y-4">
2064
+ <p class="text-[11px] text-[var(--text-muted)] text-center">Enter your 4-digit Team PIN to access all videos and update live YouTube thumbnails.</p>
2065
+
2066
+ <div class="space-y-1.5 text-center">
2067
  <input
2068
+ type="password"
2069
+ id="editorPinInput"
2070
+ maxlength="10"
2071
+ placeholder="••••"
2072
+ class="w-full p-3 text-center tracking-[12px] font-mono font-bold text-2xl border-2 border-sky-500/60 bg-[var(--bg-card)] text-[var(--text-main)] focus:outline-none focus:border-sky-400"
2073
+ onkeydown="if(event.key==='Enter') submitPinLogin()"
2074
+ autocomplete="off"
2075
  />
2076
+ <div class="flex items-center justify-between text-[10px] text-[var(--text-muted)] px-1">
2077
+ <span>Passcode / PIN</span>
2078
+ <button
2079
+ type="button"
2080
+ onclick="const el=document.getElementById('editorPinInput'); el.type = el.type === 'password' ? 'text' : 'password';"
2081
+ class="hover:text-sky-400 underline cursor-pointer"
2082
+ >
2083
+ Show / Hide
2084
+ </button>
 
 
 
 
 
 
 
 
 
 
2085
  </div>
 
2086
  </div>
2087
 
2088
  <div class="space-y-1">
2089
+ <label class="text-[9px] font-bold uppercase text-[var(--text-muted)] tracking-wider">Your Name (Optional)</label>
2090
  <input
2091
  type="text"
2092
+ id="editorNameInput"
2093
+ placeholder="e.g. Editor / Aniket"
2094
+ class="w-full p-2 text-xs border border-[var(--border-main)] bg-[var(--bg-card)] text-[var(--text-main)] focus:outline-none focus:border-sky-400"
2095
+ onkeydown="if(event.key==='Enter') submitPinLogin()"
 
2096
  />
2097
  </div>
2098
 
2099
  <button
2100
  type="button"
2101
+ id="pinLoginSubmitBtn"
2102
+ onclick="submitPinLogin()"
2103
+ class="w-full py-3 bg-sky-600 hover:bg-sky-500 text-white font-bold text-xs uppercase tracking-widest cursor-pointer transition-all shadow-md flex items-center justify-center gap-2"
2104
  >
2105
+ <span>ENTER STUDIO ➔</span>
2106
  </button>
2107
 
2108
+ <div class="p-2 bg-[var(--bg-subcard)] border border-[var(--border-main)] text-center text-[10px] text-[var(--text-muted)]">
2109
+ <span>Default PIN is <button type="button" onclick="document.getElementById('editorPinInput').value='1234'; submitPinLogin('1234');" class="text-sky-400 font-mono font-bold underline cursor-pointer">1234</button></span>
2110
  </div>
2111
  </div>
2112
 
 
3933
 
3934
  loadClientIds();
3935
  loadServerSettings();
3936
+ loadTeamPin();
3937
  loadTeamEditors();
3938
  loadSmtpSettings();
3939
  showAuthLanding();
 
4057
  navEditorBadge.classList.remove('hidden');
4058
  navEditorBadge.classList.add('flex');
4059
  const label = document.getElementById('navEditorEmailLabel');
4060
+ if (label) label.textContent = `Editor (${window.editorEmail || 'PIN Verified'})`;
4061
  }
4062
  } else if (isAuth) {
4063
  if (navAuthBtn) { navAuthBtn.classList.add('hidden'); navAuthBtn.classList.remove('flex'); }
 
4081
  };
4082
 
4083
  // ══════════════════════════════════════════════════════════════════
4084
+ // TEAM & EDITOR ACCESS ENGINE (4-DIGIT PIN & 1-CLICK MAGIC LINK)
4085
  // ══════════════════════════════════════════════════════════════════
4086
 
4087
+ window.openPinLoginModal = function() {
4088
  closeAllModals();
4089
+ const modal = document.getElementById('pinAuthModal');
4090
+ if (modal) modal.classList.remove('hidden');
4091
+ const pinInput = document.getElementById('editorPinInput');
4092
+ if (pinInput) {
4093
+ pinInput.value = '';
4094
+ setTimeout(() => pinInput.focus(), 60);
4095
+ }
4096
+ };
4097
+
4098
+ window.closePinLoginModal = function() {
4099
+ const modal = document.getElementById('pinAuthModal');
4100
+ if (modal) modal.classList.add('hidden');
4101
+ };
4102
+
4103
+ window.submitPinLogin = async function(customPin = null) {
4104
+ const pinInput = document.getElementById('editorPinInput');
4105
+ const nameInput = document.getElementById('editorNameInput');
4106
+ const btn = document.getElementById('pinLoginSubmitBtn');
4107
+ const pin = customPin || pinInput?.value?.trim();
4108
+ const name = nameInput?.value?.trim() || 'Team Editor';
4109
+
4110
+ if (!pin) {
4111
+ showToast('Please enter the 4-digit PIN.', 'error');
4112
+ if (pinInput) pinInput.focus();
4113
+ return;
4114
+ }
4115
+
4116
+ if (btn) {
4117
+ btn.disabled = true;
4118
+ btn.textContent = 'VERIFYING PIN...';
4119
+ }
4120
+
4121
+ try {
4122
+ const res = await fetch('/api/auth/pin-login', {
4123
+ method: 'POST',
4124
+ headers: { 'Content-Type': 'application/json' },
4125
+ body: JSON.stringify({ pin, name })
4126
+ });
4127
+ const data = await res.json();
4128
+
4129
+ if (data.success && data.token) {
4130
+ AppStorage.setItem('editor_token', data.token);
4131
+ AppStorage.setItem('editor_name', data.name || name);
4132
+ AppStorage.setItem('editor_last_pin', pin);
4133
+
4134
+ window.gAccessToken = data.token;
4135
+ window.isAuthenticated = true;
4136
+ window.isEditorSession = true;
4137
+ window.editorEmail = data.name || name;
4138
+
4139
+ closePinLoginModal();
4140
+ checkAuthStatus();
4141
+ connectSSE();
4142
+ showToast(`✔ PIN Verified! Welcome to Studio.`, 'success');
4143
+ loadJobStatus();
4144
+ } else {
4145
+ showToast(data.error || 'Incorrect PIN code. Please try again.', 'error');
4146
+ if (pinInput) {
4147
+ pinInput.value = '';
4148
+ pinInput.focus();
4149
+ }
4150
+ }
4151
+ } catch (err) {
4152
+ showToast('Login error: ' + err.message, 'error');
4153
+ } finally {
4154
+ if (btn) {
4155
+ btn.disabled = false;
4156
+ btn.textContent = 'ENTER STUDIO ➔';
4157
+ }
4158
+ }
4159
+ };
4160
+
4161
+ window.loadTeamPin = async function() {
4162
+ const pinInput = document.getElementById('teamPinInput');
4163
+ const pinBadge = document.getElementById('currentPinBadge');
4164
+ try {
4165
+ const res = await fetch('/api/settings/pin');
4166
+ const data = await res.json();
4167
+ if (data.success && data.pin) {
4168
+ if (pinInput) pinInput.value = data.pin;
4169
+ if (pinBadge) pinBadge.textContent = `PIN: ${data.pin}`;
4170
+ }
4171
+ } catch (e) {}
4172
+ };
4173
+
4174
+ window.saveTeamPin = async function() {
4175
+ const pinInput = document.getElementById('teamPinInput');
4176
+ const btn = document.getElementById('savePinBtn');
4177
+ const pin = pinInput?.value?.trim();
4178
+
4179
+ if (!pin || pin.length < 4) {
4180
+ showToast('PIN must be at least 4 digits.', 'error');
4181
+ if (pinInput) pinInput.focus();
4182
+ return;
4183
+ }
4184
+
4185
+ if (btn) {
4186
+ btn.disabled = true;
4187
+ btn.textContent = 'SAVING...';
4188
+ }
4189
+
4190
+ try {
4191
+ const res = await fetch('/api/settings/pin', {
4192
+ method: 'POST',
4193
+ headers: { 'Content-Type': 'application/json' },
4194
+ body: JSON.stringify({ pin })
4195
+ });
4196
+ const data = await res.json();
4197
+
4198
+ if (data.success) {
4199
+ showToast(`✔ Team PIN updated to: ${data.pin}`, 'success');
4200
+ loadTeamPin();
4201
+ } else {
4202
+ showToast(data.error || 'Failed to update PIN', 'error');
4203
+ }
4204
+ } catch (err) {
4205
+ showToast('Error saving PIN: ' + err.message, 'error');
4206
+ } finally {
4207
+ if (btn) {
4208
+ btn.disabled = false;
4209
+ btn.textContent = 'Save PIN';
4210
+ }
4211
+ }
4212
+ };
4213
+
4214
+ window.copyMagicEditorLink = async function() {
4215
+ const pinInput = document.getElementById('teamPinInput');
4216
+ const pin = pinInput?.value?.trim() || '1234';
4217
+ const magicUrl = `${window.location.origin}/?pin=${encodeURIComponent(pin)}`;
4218
+ await copyToClipboard(magicUrl, '1-Click Team Editor Link');
4219
+ };
4220
  if (modal) modal.classList.remove('hidden');
4221
  const emailInput = document.getElementById('editorEmailInput');
4222
  if (emailInput) {
 
7398
  // Check for auth callback redirect first
7399
  checkServerAuthOnLoad();
7400
 
7401
+ // Check for 1-Click Magic Link (?pin=1234 or ?access=1234)
7402
+ const urlParams = new URLSearchParams(window.location.search);
7403
+ const urlPin = urlParams.get('pin') || urlParams.get('access') || urlParams.get('key');
7404
+ if (urlPin) {
7405
+ console.log('[auth] 1-Click PIN detected in URL, auto-logging in...');
7406
+ submitPinLogin(urlPin);
7407
+ try { window.history.replaceState({}, document.title, window.location.pathname); } catch (e) {}
7408
+ }
7409
+
7410
+ // Auth Priority: 1) Team Editor session (4-Digit PIN), 2) Server-side refresh token, 3) localStorage access token, 4) Silent GIS refresh
7411
  (async () => {
7412
  const isEditor = await checkEditorAuthOnLoad();
7413
  if (isEditor) {
server.js CHANGED
@@ -2263,6 +2263,83 @@ app.post('/api/auth/verify-otp', async (req, res) => {
2263
  }
2264
  });
2265
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2266
  // 6. GET /api/auth/editor-session — Check current editor session
2267
  app.get('/api/auth/editor-session', (req, res) => {
2268
  const authHeader = req.headers.authorization || '';
 
2263
  }
2264
  });
2265
 
2266
+ // 5B. POST /api/auth/pin-login — Verify 4-digit PIN and issue persistent editor session token
2267
+ app.post('/api/auth/pin-login', async (req, res) => {
2268
+ try {
2269
+ const { pin, name } = req.body || {};
2270
+ const storedPin = db.getSetting('team_pin') || process.env.TEAM_PIN || '1234';
2271
+
2272
+ if (!pin || String(pin).trim() !== String(storedPin).trim()) {
2273
+ return res.status(401).json({
2274
+ success: false,
2275
+ error: 'Incorrect Passcode / PIN. Please enter the valid PIN.'
2276
+ });
2277
+ }
2278
+
2279
+ const editorName = (name || 'Team Editor').trim();
2280
+
2281
+ // Pre-resolve channel title & ID
2282
+ let targetChannelId = null;
2283
+ let channelTitle = 'YouTube Channel';
2284
+
2285
+ try {
2286
+ const creds = db.getActiveCredentials();
2287
+ if (creds && creds.length > 0) {
2288
+ const cred = creds[0];
2289
+ const oauth2Client = new google.auth.OAuth2(cred.clientId, cred.clientSecret);
2290
+ oauth2Client.setCredentials({ refresh_token: cred.refreshToken });
2291
+ const yt = google.youtube({ version: 'v3', auth: oauth2Client });
2292
+ const chRes = await yt.channels.list({ part: ['snippet', 'id'], mine: true });
2293
+ if (chRes.data.items && chRes.data.items.length > 0) {
2294
+ targetChannelId = chRes.data.items[0].id;
2295
+ channelTitle = chRes.data.items[0].snippet?.title || 'YouTube Channel';
2296
+ }
2297
+ }
2298
+ } catch (e) {
2299
+ console.warn('Could not pre-resolve channel for PIN editor session:', e.message);
2300
+ }
2301
+
2302
+ if (!targetChannelId) {
2303
+ const history = db.loadUploadedHistory();
2304
+ if (history && history.length > 0 && history[0].channelId) {
2305
+ targetChannelId = history[0].channelId;
2306
+ channelTitle = history[0].batch || 'YouTube Channel';
2307
+ }
2308
+ }
2309
+
2310
+ const session = db.createEditorSession(editorName, 'editor', targetChannelId, null, 30);
2311
+
2312
+ return res.json({
2313
+ success: true,
2314
+ token: session.token,
2315
+ name: editorName,
2316
+ role: session.role,
2317
+ channelId: targetChannelId,
2318
+ channelTitle,
2319
+ message: `PIN Verified! Welcome ${editorName} to Studio.`
2320
+ });
2321
+ } catch (err) {
2322
+ console.error('PIN Login error:', err);
2323
+ return res.status(500).json({ success: false, error: err.message });
2324
+ }
2325
+ });
2326
+
2327
+ // 5C. GET /api/settings/pin & POST /api/settings/pin
2328
+ app.get('/api/settings/pin', (req, res) => {
2329
+ const currentPin = db.getSetting('team_pin') || process.env.TEAM_PIN || '1234';
2330
+ res.json({ success: true, pin: currentPin });
2331
+ });
2332
+
2333
+ app.post('/api/settings/pin', (req, res) => {
2334
+ const { pin } = req.body || {};
2335
+ if (!pin || String(pin).trim().length < 4) {
2336
+ return res.status(400).json({ success: false, error: 'PIN must be at least 4 digits or characters.' });
2337
+ }
2338
+ const cleanPin = String(pin).trim();
2339
+ db.setSetting('team_pin', cleanPin);
2340
+ res.json({ success: true, pin: cleanPin, message: `Team PIN successfully updated to: ${cleanPin}` });
2341
+ });
2342
+
2343
  // 6. GET /api/auth/editor-session — Check current editor session
2344
  app.get('/api/auth/editor-session', (req, res) => {
2345
  const authHeader = req.headers.authorization || '';