nskd-lbr 1.1.1 → 1.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -147
- package/package.json +4 -4
- package/src/nskd-lbr.js +0 -764
- package/src/nskd-lbr.min.js +7 -443
- package/src/login.svg +0 -1
package/src/nskd-lbr.js
CHANGED
|
@@ -10,9 +10,6 @@
|
|
|
10
10
|
* @param {integer} [options.timeout=10000] - API request timeout in milliseconds
|
|
11
11
|
* @param {boolean} [options.useLegacyAPI=false] - Whether to use the legacy API format
|
|
12
12
|
* @param {function} [options.onLog=null] - Whether to validate local data against API response
|
|
13
|
-
* @param {string} [options.loginEndpoint=''] - Login API endpoint for authentication
|
|
14
|
-
* @param {function} [options.onLoginSuccess=null] - Callback function for successful login
|
|
15
|
-
* @param {function} [options.onLoginFail=null] - Callback function for failed login
|
|
16
13
|
*/
|
|
17
14
|
|
|
18
15
|
class NskdLbr {
|
|
@@ -23,14 +20,10 @@ class NskdLbr {
|
|
|
23
20
|
this.strictCheck = options.strictCheck !== undefined ? options.strictCheck : true;
|
|
24
21
|
this.onLog = options.onLog || null;
|
|
25
22
|
this.useLegacyAPI = options.useLegacyAPI || false;
|
|
26
|
-
this.loginEndpoint = options.loginEndpoint || '';
|
|
27
|
-
this.onLoginSuccess = options.onLoginSuccess || null;
|
|
28
|
-
this.onLoginFail = options.onLoginFail || null;
|
|
29
23
|
this.certificateData = null;
|
|
30
24
|
this.verificationKey = null;
|
|
31
25
|
this.localData = null;
|
|
32
26
|
this.isValid = false;
|
|
33
|
-
this.currentLoginModal = null;
|
|
34
27
|
}
|
|
35
28
|
|
|
36
29
|
/**
|
|
@@ -62,762 +55,6 @@ class NskdLbr {
|
|
|
62
55
|
}
|
|
63
56
|
}
|
|
64
57
|
|
|
65
|
-
/**
|
|
66
|
-
* Show login modal (only available for webpages with loginEndpoint set)
|
|
67
|
-
* @returns {Promise<Object>} Login result
|
|
68
|
-
*/
|
|
69
|
-
async showLoginModal() {
|
|
70
|
-
if (typeof window === 'undefined') {
|
|
71
|
-
throw new Error('Login modal is only available in browser environments');
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
if (!this.loginEndpoint || this.loginEndpoint.trim() === '') {
|
|
75
|
-
throw new Error('Login endpoint is not configured');
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
return new Promise((resolve, reject) => {
|
|
79
|
-
this.createLoginModal(resolve, reject);
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Create and display the login modal
|
|
85
|
-
* @private
|
|
86
|
-
*/
|
|
87
|
-
createLoginModal(resolve, reject) {
|
|
88
|
-
if (this.currentLoginModal) {
|
|
89
|
-
this.closeLoginModal();
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const overlay = document.createElement('div');
|
|
93
|
-
overlay.id = 'noskid-login-overlay';
|
|
94
|
-
overlay.style.cssText = `
|
|
95
|
-
position: fixed;
|
|
96
|
-
top: 0;
|
|
97
|
-
left: 0;
|
|
98
|
-
width: 100%;
|
|
99
|
-
height: 100%;
|
|
100
|
-
background: rgba(139, 69, 19, 0.15);
|
|
101
|
-
display: flex;
|
|
102
|
-
justify-content: center;
|
|
103
|
-
align-items: center;
|
|
104
|
-
z-index: 10000;
|
|
105
|
-
backdrop-filter: blur(8px);
|
|
106
|
-
opacity: 0;
|
|
107
|
-
transition: opacity 0.3s ease;
|
|
108
|
-
`;
|
|
109
|
-
|
|
110
|
-
const modal = document.createElement('div');
|
|
111
|
-
modal.id = 'noskid-login-modal';
|
|
112
|
-
modal.style.cssText = `
|
|
113
|
-
background: linear-gradient(135deg, #fcfaf5 0%, #f0ede0 100%);
|
|
114
|
-
border: 2px solid #8b4513;
|
|
115
|
-
border-radius: 16px;
|
|
116
|
-
padding: 0;
|
|
117
|
-
max-width: 480px;
|
|
118
|
-
width: 95%;
|
|
119
|
-
max-height: 95vh;
|
|
120
|
-
overflow: hidden;
|
|
121
|
-
box-shadow:
|
|
122
|
-
0 25px 50px -12px rgba(139, 69, 19, 0.25),
|
|
123
|
-
0 0 0 1px rgba(139, 69, 19, 0.1),
|
|
124
|
-
inset 0 1px 0 rgba(252, 250, 245, 0.9);
|
|
125
|
-
transform: translateY(20px) scale(0.95);
|
|
126
|
-
transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
|
|
127
|
-
position: relative;
|
|
128
|
-
`;
|
|
129
|
-
|
|
130
|
-
const style = document.createElement('style');
|
|
131
|
-
style.textContent = `
|
|
132
|
-
@keyframes noskidModalSlideIn {
|
|
133
|
-
from {
|
|
134
|
-
opacity: 0;
|
|
135
|
-
transform: translateY(20px) scale(0.95);
|
|
136
|
-
}
|
|
137
|
-
to {
|
|
138
|
-
opacity: 1;
|
|
139
|
-
transform: translateY(0) scale(1);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
@keyframes noskidPulse {
|
|
144
|
-
0%, 100% { transform: scale(1); }
|
|
145
|
-
50% { transform: scale(1.05); }
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
@keyframes noskidShake {
|
|
149
|
-
0%, 100% { transform: translateX(0); }
|
|
150
|
-
25% { transform: translateX(-4px); }
|
|
151
|
-
75% { transform: translateX(4px); }
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
.noskid-modal-active {
|
|
155
|
-
opacity: 1 !important;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
.noskid-modal-active #noskid-login-modal {
|
|
159
|
-
transform: translateY(0) scale(1) !important;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
.noskid-header {
|
|
163
|
-
background: linear-gradient(135deg, #f9f7f0 0%, #e8e5d8 100%);
|
|
164
|
-
border-bottom: 1px solid #8b4513;
|
|
165
|
-
padding: 24px;
|
|
166
|
-
text-align: center;
|
|
167
|
-
position: relative;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
.noskid-header::before {
|
|
171
|
-
content: '';
|
|
172
|
-
position: absolute;
|
|
173
|
-
top: 0;
|
|
174
|
-
left: 0;
|
|
175
|
-
right: 0;
|
|
176
|
-
height: 1px;
|
|
177
|
-
background: linear-gradient(90deg, transparent, rgba(139, 69, 19, 0.3), transparent);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
.noskid-content {
|
|
181
|
-
padding: 32px;
|
|
182
|
-
overflow-y: auto;
|
|
183
|
-
max-height: calc(95vh - 140px);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
.noskid-title {
|
|
187
|
-
margin: 0;
|
|
188
|
-
color: #8b4513;
|
|
189
|
-
font-size: 24px;
|
|
190
|
-
font-family: 'Times New Roman', serif;
|
|
191
|
-
font-weight: bold;
|
|
192
|
-
display: flex;
|
|
193
|
-
align-items: center;
|
|
194
|
-
justify-content: center;
|
|
195
|
-
gap: 12px;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
.noskid-subtitle {
|
|
199
|
-
margin: 8px 0 0 0;
|
|
200
|
-
color: rgba(139, 69, 19, 0.7);
|
|
201
|
-
font-size: 14px;
|
|
202
|
-
font-family: system-ui, -apple-system, sans-serif;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
.noskid-icon {
|
|
206
|
-
width: 28px;
|
|
207
|
-
height: 28px;
|
|
208
|
-
background: #8b4513;
|
|
209
|
-
border-radius: 50%;
|
|
210
|
-
display: flex;
|
|
211
|
-
align-items: center;
|
|
212
|
-
justify-content: center;
|
|
213
|
-
color: #fcfaf5;
|
|
214
|
-
font-size: 16px;
|
|
215
|
-
box-shadow: 0 2px 4px rgba(139, 69, 19, 0.2);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
.noskid-form-group {
|
|
219
|
-
margin-bottom: 24px;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
.noskid-label {
|
|
223
|
-
display: block;
|
|
224
|
-
margin-bottom: 8px;
|
|
225
|
-
color: #8b4513;
|
|
226
|
-
font-size: 14px;
|
|
227
|
-
font-weight: 600;
|
|
228
|
-
font-family: system-ui, -apple-system, sans-serif;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
.noskid-cert-upload {
|
|
232
|
-
border: 2px dashed rgba(139, 69, 19, 0.3);
|
|
233
|
-
border-radius: 12px;
|
|
234
|
-
padding: 24px;
|
|
235
|
-
text-align: center;
|
|
236
|
-
background: linear-gradient(135deg, rgba(252, 250, 245, 0.5), rgba(240, 237, 224, 0.5));
|
|
237
|
-
transition: all 0.3s ease;
|
|
238
|
-
cursor: pointer;
|
|
239
|
-
position: relative;
|
|
240
|
-
min-height: 120px;
|
|
241
|
-
display: flex;
|
|
242
|
-
flex-direction: column;
|
|
243
|
-
align-items: center;
|
|
244
|
-
justify-content: center;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
.noskid-cert-upload:hover {
|
|
248
|
-
border-color: #8b4513;
|
|
249
|
-
background: linear-gradient(135deg, rgba(252, 250, 245, 0.8), rgba(240, 237, 224, 0.8));
|
|
250
|
-
transform: translateY(-2px);
|
|
251
|
-
box-shadow: 0 4px 12px rgba(139, 69, 19, 0.1);
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
.noskid-cert-upload.dragover {
|
|
255
|
-
border-color: #8b4513;
|
|
256
|
-
background: linear-gradient(135deg, rgba(139, 69, 19, 0.1), rgba(139, 69, 19, 0.05));
|
|
257
|
-
animation: noskidPulse 1s infinite;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
.noskid-cert-upload.error {
|
|
261
|
-
border-color: #dc2626;
|
|
262
|
-
animation: noskidShake 0.5s ease-in-out;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
.noskid-cert-upload.success {
|
|
266
|
-
border-color: #059669;
|
|
267
|
-
background: linear-gradient(135deg, rgba(5, 150, 105, 0.1), rgba(5, 150, 105, 0.05));
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
.noskid-upload-icon {
|
|
271
|
-
width: 48px;
|
|
272
|
-
height: 48px;
|
|
273
|
-
margin-bottom: 12px;
|
|
274
|
-
background: rgba(139, 69, 19, 0.1);
|
|
275
|
-
border-radius: 50%;
|
|
276
|
-
display: flex;
|
|
277
|
-
align-items: center;
|
|
278
|
-
justify-content: center;
|
|
279
|
-
color: #8b4513;
|
|
280
|
-
font-size: 24px;
|
|
281
|
-
transition: all 0.3s ease;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
.noskid-cert-upload:hover .noskid-upload-icon {
|
|
285
|
-
background: rgba(139, 69, 19, 0.2);
|
|
286
|
-
transform: scale(1.1);
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
.noskid-upload-text {
|
|
290
|
-
color: #8b4513;
|
|
291
|
-
font-size: 16px;
|
|
292
|
-
font-weight: 600;
|
|
293
|
-
margin-bottom: 4px;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
.noskid-upload-hint {
|
|
297
|
-
color: rgba(139, 69, 19, 0.6);
|
|
298
|
-
font-size: 12px;
|
|
299
|
-
line-height: 1.4;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
.noskid-file-input {
|
|
303
|
-
position: absolute;
|
|
304
|
-
top: 0;
|
|
305
|
-
left: 0;
|
|
306
|
-
width: 100%;
|
|
307
|
-
height: 100%;
|
|
308
|
-
opacity: 0;
|
|
309
|
-
cursor: pointer;
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
.noskid-input {
|
|
313
|
-
width: 100%;
|
|
314
|
-
padding: 16px;
|
|
315
|
-
border: 2px solid rgba(139, 69, 19, 0.2);
|
|
316
|
-
border-radius: 12px;
|
|
317
|
-
font-size: 16px;
|
|
318
|
-
box-sizing: border-box;
|
|
319
|
-
background: rgba(252, 250, 245, 0.8);
|
|
320
|
-
color: #8b4513;
|
|
321
|
-
font-family: system-ui, -apple-system, sans-serif;
|
|
322
|
-
transition: all 0.3s ease;
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
.noskid-input:focus {
|
|
326
|
-
outline: none;
|
|
327
|
-
border-color: #8b4513;
|
|
328
|
-
box-shadow: 0 0 0 4px rgba(139, 69, 19, 0.1);
|
|
329
|
-
background: #fcfaf5;
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
.noskid-input::placeholder {
|
|
333
|
-
color: rgba(139, 69, 19, 0.5);
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
.noskid-btn {
|
|
337
|
-
background: linear-gradient(135deg, #8b4513 0%, #6d3410 100%);
|
|
338
|
-
color: #fcfaf5;
|
|
339
|
-
border: none;
|
|
340
|
-
padding: 16px 32px;
|
|
341
|
-
border-radius: 12px;
|
|
342
|
-
cursor: pointer;
|
|
343
|
-
font-size: 16px;
|
|
344
|
-
font-weight: 600;
|
|
345
|
-
font-family: system-ui, -apple-system, sans-serif;
|
|
346
|
-
transition: all 0.3s ease;
|
|
347
|
-
box-shadow: 0 4px 12px rgba(139, 69, 19, 0.2);
|
|
348
|
-
position: relative;
|
|
349
|
-
overflow: hidden;
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
.noskid-btn::before {
|
|
353
|
-
content: '';
|
|
354
|
-
position: absolute;
|
|
355
|
-
top: 0;
|
|
356
|
-
left: -100%;
|
|
357
|
-
width: 100%;
|
|
358
|
-
height: 100%;
|
|
359
|
-
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
|
|
360
|
-
transition: left 0.5s ease;
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
.noskid-btn:hover::before {
|
|
364
|
-
left: 100%;
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
.noskid-btn:hover {
|
|
368
|
-
background: linear-gradient(135deg, #6d3410 0%, #5a2b0d 100%);
|
|
369
|
-
transform: translateY(-2px);
|
|
370
|
-
box-shadow: 0 6px 20px rgba(139, 69, 19, 0.3);
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
.noskid-btn:active {
|
|
374
|
-
transform: translateY(0);
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
.noskid-btn:disabled {
|
|
378
|
-
background: rgba(139, 69, 19, 0.3);
|
|
379
|
-
cursor: not-allowed;
|
|
380
|
-
transform: none;
|
|
381
|
-
box-shadow: none;
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
.noskid-btn:disabled::before {
|
|
385
|
-
display: none;
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
.noskid-btn-secondary {
|
|
389
|
-
background: linear-gradient(135deg, rgba(139, 69, 19, 0.1) 0%, rgba(139, 69, 19, 0.2) 100%);
|
|
390
|
-
color: #8b4513;
|
|
391
|
-
border: 2px solid rgba(139, 69, 19, 0.3);
|
|
392
|
-
padding: 14px 30px;
|
|
393
|
-
border-radius: 12px;
|
|
394
|
-
cursor: pointer;
|
|
395
|
-
font-size: 16px;
|
|
396
|
-
font-weight: 600;
|
|
397
|
-
font-family: system-ui, -apple-system, sans-serif;
|
|
398
|
-
transition: all 0.3s ease;
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
.noskid-btn-secondary:hover {
|
|
402
|
-
background: linear-gradient(135deg, rgba(139, 69, 19, 0.2) 0%, rgba(139, 69, 19, 0.3) 100%);
|
|
403
|
-
border-color: #8b4513;
|
|
404
|
-
transform: translateY(-2px);
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
.noskid-error {
|
|
408
|
-
color: #dc2626;
|
|
409
|
-
font-size: 14px;
|
|
410
|
-
margin-top: 8px;
|
|
411
|
-
padding: 12px;
|
|
412
|
-
background: rgba(220, 38, 38, 0.1);
|
|
413
|
-
border: 1px solid rgba(220, 38, 38, 0.2);
|
|
414
|
-
border-radius: 8px;
|
|
415
|
-
font-family: system-ui, -apple-system, sans-serif;
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
.noskid-success {
|
|
419
|
-
color: #059669;
|
|
420
|
-
font-size: 14px;
|
|
421
|
-
margin-top: 8px;
|
|
422
|
-
padding: 12px;
|
|
423
|
-
background: rgba(5, 150, 105, 0.1);
|
|
424
|
-
border: 1px solid rgba(5, 150, 105, 0.2);
|
|
425
|
-
border-radius: 8px;
|
|
426
|
-
font-family: system-ui, -apple-system, sans-serif;
|
|
427
|
-
display: flex;
|
|
428
|
-
align-items: center;
|
|
429
|
-
gap: 8px;
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
.noskid-actions {
|
|
433
|
-
display: flex;
|
|
434
|
-
gap: 16px;
|
|
435
|
-
justify-content: flex-end;
|
|
436
|
-
margin-top: 32px;
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
.noskid-cert-info {
|
|
440
|
-
background: rgba(139, 69, 19, 0.05);
|
|
441
|
-
border: 1px solid rgba(139, 69, 19, 0.2);
|
|
442
|
-
border-radius: 8px;
|
|
443
|
-
padding: 16px;
|
|
444
|
-
margin-top: 12px;
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
.noskid-cert-user {
|
|
448
|
-
font-weight: 600;
|
|
449
|
-
color: #8b4513;
|
|
450
|
-
margin-bottom: 4px;
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
.noskid-cert-details {
|
|
454
|
-
font-size: 12px;
|
|
455
|
-
color: rgba(139, 69, 19, 0.7);
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
.noskid-paste-hint {
|
|
459
|
-
margin-top: 8px;
|
|
460
|
-
font-size: 11px;
|
|
461
|
-
color: rgba(139, 69, 19, 0.5);
|
|
462
|
-
text-align: center;
|
|
463
|
-
font-style: italic;
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
@media (max-width: 600px) {
|
|
467
|
-
.noskid-content {
|
|
468
|
-
padding: 24px;
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
.noskid-header {
|
|
472
|
-
padding: 20px;
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
.noskid-title {
|
|
476
|
-
font-size: 20px;
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
.noskid-actions {
|
|
480
|
-
flex-direction: column-reverse;
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
.noskid-btn,
|
|
484
|
-
.noskid-btn-secondary {
|
|
485
|
-
width: 100%;
|
|
486
|
-
justify-content: center;
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
`;
|
|
490
|
-
document.head.appendChild(style);
|
|
491
|
-
|
|
492
|
-
modal.innerHTML = `
|
|
493
|
-
<div class="noskid-header">
|
|
494
|
-
<h2 class="noskid-title">
|
|
495
|
-
<div class="noskid-icon">🔐</div>
|
|
496
|
-
Login with NoSkid
|
|
497
|
-
</h2>
|
|
498
|
-
<p class="noskid-subtitle">Upload your certificate and enter your password to authenticate</p>
|
|
499
|
-
</div>
|
|
500
|
-
|
|
501
|
-
<div class="noskid-content">
|
|
502
|
-
<div class="noskid-form-group">
|
|
503
|
-
<label class="noskid-label">Certificate File</label>
|
|
504
|
-
<div class="noskid-cert-upload" id="noskid-cert-upload">
|
|
505
|
-
<div class="noskid-upload-icon">📄</div>
|
|
506
|
-
<div class="noskid-upload-text">Drop certificate here or click to browse</div>
|
|
507
|
-
<div class="noskid-upload-hint">
|
|
508
|
-
Supports .png files • Drag & drop • Paste from clipboard<br>
|
|
509
|
-
<kbd>Ctrl+V</kbd> to paste certificate
|
|
510
|
-
</div>
|
|
511
|
-
<input type="file" id="noskid-cert-file" accept=".png" class="noskid-file-input">
|
|
512
|
-
</div>
|
|
513
|
-
<div id="noskid-file-error" class="noskid-error" style="display: none;"></div>
|
|
514
|
-
<div id="noskid-file-success" class="noskid-success" style="display: none;">
|
|
515
|
-
<span>✓</span>
|
|
516
|
-
<div id="noskid-cert-info"></div>
|
|
517
|
-
</div>
|
|
518
|
-
</div>
|
|
519
|
-
|
|
520
|
-
<div class="noskid-form-group">
|
|
521
|
-
<label class="noskid-label">Password</label>
|
|
522
|
-
<input type="password" id="noskid-password" class="noskid-input" placeholder="Enter your password">
|
|
523
|
-
<div id="noskid-password-error" class="noskid-error" style="display: none;"></div>
|
|
524
|
-
</div>
|
|
525
|
-
|
|
526
|
-
<div id="noskid-login-error" class="noskid-error" style="display: none;"></div>
|
|
527
|
-
|
|
528
|
-
<div class="noskid-actions">
|
|
529
|
-
<button id="noskid-cancel-btn" class="noskid-btn-secondary">Cancel</button>
|
|
530
|
-
<button id="noskid-login-btn" class="noskid-btn" disabled>
|
|
531
|
-
<span>Login</span>
|
|
532
|
-
</button>
|
|
533
|
-
</div>
|
|
534
|
-
</div>
|
|
535
|
-
`;
|
|
536
|
-
|
|
537
|
-
overlay.appendChild(modal);
|
|
538
|
-
document.body.appendChild(overlay);
|
|
539
|
-
this.currentLoginModal = overlay;
|
|
540
|
-
|
|
541
|
-
requestAnimationFrame(() => {
|
|
542
|
-
overlay.classList.add('noskid-modal-active');
|
|
543
|
-
});
|
|
544
|
-
|
|
545
|
-
const uploadArea = modal.querySelector('#noskid-cert-upload');
|
|
546
|
-
const fileInput = modal.querySelector('#noskid-cert-file');
|
|
547
|
-
const passwordInput = modal.querySelector('#noskid-password');
|
|
548
|
-
const loginBtn = modal.querySelector('#noskid-login-btn');
|
|
549
|
-
const cancelBtn = modal.querySelector('#noskid-cancel-btn');
|
|
550
|
-
const fileError = modal.querySelector('#noskid-file-error');
|
|
551
|
-
const fileSuccess = modal.querySelector('#noskid-file-success');
|
|
552
|
-
const certInfo = modal.querySelector('#noskid-cert-info');
|
|
553
|
-
const passwordError = modal.querySelector('#noskid-password-error');
|
|
554
|
-
const loginError = modal.querySelector('#noskid-login-error');
|
|
555
|
-
|
|
556
|
-
let certificateValid = false;
|
|
557
|
-
let tempCertData = null;
|
|
558
|
-
|
|
559
|
-
const processFile = async (file) => {
|
|
560
|
-
fileError.style.display = 'none';
|
|
561
|
-
fileSuccess.style.display = 'none';
|
|
562
|
-
uploadArea.classList.remove('error', 'success');
|
|
563
|
-
certificateValid = false;
|
|
564
|
-
tempCertData = null;
|
|
565
|
-
updateLoginButton();
|
|
566
|
-
|
|
567
|
-
if (!file) return;
|
|
568
|
-
|
|
569
|
-
try {
|
|
570
|
-
this.nskdLbrLog('Processing certificate for login...', 'info');
|
|
571
|
-
|
|
572
|
-
const tempInstance = new NskdLbr({
|
|
573
|
-
apiUrl: this.apiUrl,
|
|
574
|
-
debug: this.debug,
|
|
575
|
-
timeout: this.timeout,
|
|
576
|
-
strictCheck: this.strictCheck,
|
|
577
|
-
useLegacyAPI: this.useLegacyAPI
|
|
578
|
-
});
|
|
579
|
-
|
|
580
|
-
const result = await tempInstance.loadFromFile(file);
|
|
581
|
-
|
|
582
|
-
if (result.valid) {
|
|
583
|
-
certificateValid = true;
|
|
584
|
-
tempCertData = tempInstance.getCertificateData();
|
|
585
|
-
|
|
586
|
-
certInfo.innerHTML = `
|
|
587
|
-
<div class="noskid-cert-user">${tempCertData.localUsername || 'Unknown User'}</div>
|
|
588
|
-
<div class="noskid-cert-details">
|
|
589
|
-
Certificate verified • ${(file.size / 1024).toFixed(1)} KB
|
|
590
|
-
</div>
|
|
591
|
-
`;
|
|
592
|
-
|
|
593
|
-
uploadArea.classList.add('success');
|
|
594
|
-
uploadArea.querySelector('.noskid-upload-text').textContent = 'Certificate loaded successfully!';
|
|
595
|
-
uploadArea.querySelector('.noskid-upload-icon').textContent = '✓';
|
|
596
|
-
fileSuccess.style.display = 'block';
|
|
597
|
-
|
|
598
|
-
this.nskdLbrLog('Certificate verified for login', 'success');
|
|
599
|
-
} else {
|
|
600
|
-
throw new Error(result.message || 'Certificate verification failed');
|
|
601
|
-
}
|
|
602
|
-
} catch (error) {
|
|
603
|
-
uploadArea.classList.add('error');
|
|
604
|
-
uploadArea.querySelector('.noskid-upload-text').textContent = 'Certificate verification failed';
|
|
605
|
-
uploadArea.querySelector('.noskid-upload-icon').textContent = '❌';
|
|
606
|
-
fileError.textContent = error.message;
|
|
607
|
-
fileError.style.display = 'block';
|
|
608
|
-
this.nskdLbrLog(`Certificate verification failed: ${error.message}`, 'error');
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
updateLoginButton();
|
|
612
|
-
};
|
|
613
|
-
|
|
614
|
-
fileInput.addEventListener('change', async (e) => {
|
|
615
|
-
const file = e.target.files[0];
|
|
616
|
-
await processFile(file);
|
|
617
|
-
});
|
|
618
|
-
|
|
619
|
-
uploadArea.addEventListener('dragover', (e) => {
|
|
620
|
-
e.preventDefault();
|
|
621
|
-
uploadArea.classList.add('dragover');
|
|
622
|
-
});
|
|
623
|
-
|
|
624
|
-
uploadArea.addEventListener('dragleave', (e) => {
|
|
625
|
-
e.preventDefault();
|
|
626
|
-
if (!uploadArea.contains(e.relatedTarget)) {
|
|
627
|
-
uploadArea.classList.remove('dragover');
|
|
628
|
-
}
|
|
629
|
-
});
|
|
630
|
-
|
|
631
|
-
uploadArea.addEventListener('drop', async (e) => {
|
|
632
|
-
e.preventDefault();
|
|
633
|
-
uploadArea.classList.remove('dragover');
|
|
634
|
-
|
|
635
|
-
const files = e.dataTransfer.files;
|
|
636
|
-
if (files.length > 0) {
|
|
637
|
-
const file = files[0];
|
|
638
|
-
if (file.type === 'image/png' || file.name.endsWith('.png')) {
|
|
639
|
-
await processFile(file);
|
|
640
|
-
} else {
|
|
641
|
-
uploadArea.classList.add('error');
|
|
642
|
-
fileError.textContent = 'Please drop a valid PNG certificate file';
|
|
643
|
-
fileError.style.display = 'block';
|
|
644
|
-
}
|
|
645
|
-
}
|
|
646
|
-
});
|
|
647
|
-
|
|
648
|
-
document.addEventListener('paste', async (e) => {
|
|
649
|
-
if (!overlay.classList.contains('noskid-modal-active')) return;
|
|
650
|
-
|
|
651
|
-
const items = e.clipboardData.items;
|
|
652
|
-
for (let item of items) {
|
|
653
|
-
if (item.type === 'image/png') {
|
|
654
|
-
e.preventDefault();
|
|
655
|
-
const file = item.getAsFile();
|
|
656
|
-
if (file) {
|
|
657
|
-
await processFile(file);
|
|
658
|
-
}
|
|
659
|
-
break;
|
|
660
|
-
}
|
|
661
|
-
}
|
|
662
|
-
});
|
|
663
|
-
|
|
664
|
-
passwordInput.addEventListener('input', () => {
|
|
665
|
-
passwordError.style.display = 'none';
|
|
666
|
-
loginError.style.display = 'none';
|
|
667
|
-
updateLoginButton();
|
|
668
|
-
});
|
|
669
|
-
|
|
670
|
-
const updateLoginButton = () => {
|
|
671
|
-
const hasPassword = passwordInput.value.trim().length > 0;
|
|
672
|
-
loginBtn.disabled = !certificateValid || !hasPassword;
|
|
673
|
-
|
|
674
|
-
loginBtn.innerHTML = '<span>Login</span>';
|
|
675
|
-
};
|
|
676
|
-
|
|
677
|
-
loginBtn.addEventListener('click', async () => {
|
|
678
|
-
const password = passwordInput.value.trim();
|
|
679
|
-
|
|
680
|
-
if (!certificateValid || !tempCertData) {
|
|
681
|
-
passwordError.textContent = 'Please upload a valid certificate first';
|
|
682
|
-
passwordError.style.display = 'block';
|
|
683
|
-
return;
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
if (!password) {
|
|
687
|
-
passwordError.textContent = 'Please enter your password';
|
|
688
|
-
passwordError.style.display = 'block';
|
|
689
|
-
return;
|
|
690
|
-
}
|
|
691
|
-
|
|
692
|
-
loginBtn.disabled = true;
|
|
693
|
-
loginBtn.innerHTML = '<span>Logging in...</span>';
|
|
694
|
-
loginError.style.display = 'none';
|
|
695
|
-
|
|
696
|
-
try {
|
|
697
|
-
const loginResult = await this.performLogin(tempCertData, password);
|
|
698
|
-
|
|
699
|
-
if (loginResult.success) {
|
|
700
|
-
this.nskdLbrLog('Login successful', 'success');
|
|
701
|
-
|
|
702
|
-
this.certificateData = tempCertData;
|
|
703
|
-
this.verificationKey = tempCertData.key;
|
|
704
|
-
this.isValid = true;
|
|
705
|
-
|
|
706
|
-
if (this.onLoginSuccess && typeof this.onLoginSuccess === 'function') {
|
|
707
|
-
this.onLoginSuccess(loginResult, tempCertData);
|
|
708
|
-
}
|
|
709
|
-
|
|
710
|
-
loginBtn.innerHTML = '<span>Success!</span>';
|
|
711
|
-
setTimeout(() => {
|
|
712
|
-
this.closeLoginModal();
|
|
713
|
-
resolve({ success: true, data: tempCertData, response: loginResult });
|
|
714
|
-
}, 800);
|
|
715
|
-
} else {
|
|
716
|
-
throw new Error(loginResult.message || 'Login failed');
|
|
717
|
-
}
|
|
718
|
-
} catch (error) {
|
|
719
|
-
this.nskdLbrLog(`Login failed: ${error.message}`, 'error');
|
|
720
|
-
loginError.textContent = error.message;
|
|
721
|
-
loginError.style.display = 'block';
|
|
722
|
-
|
|
723
|
-
if (this.onLoginFail && typeof this.onLoginFail === 'function') {
|
|
724
|
-
this.onLoginFail(error, tempCertData);
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
loginBtn.disabled = false;
|
|
728
|
-
loginBtn.innerHTML = '<span>Try Again</span>';
|
|
729
|
-
}
|
|
730
|
-
});
|
|
731
|
-
|
|
732
|
-
cancelBtn.addEventListener('click', () => {
|
|
733
|
-
this.nskdLbrLog('Login cancelled by user', 'info');
|
|
734
|
-
this.closeLoginModal();
|
|
735
|
-
reject(new Error('Login cancelled by user'));
|
|
736
|
-
});
|
|
737
|
-
|
|
738
|
-
overlay.addEventListener('click', (e) => {
|
|
739
|
-
if (e.target === overlay) {
|
|
740
|
-
this.nskdLbrLog('Login cancelled by clicking outside', 'info');
|
|
741
|
-
this.closeLoginModal();
|
|
742
|
-
reject(new Error('Login cancelled'));
|
|
743
|
-
}
|
|
744
|
-
});
|
|
745
|
-
|
|
746
|
-
const escHandler = (e) => {
|
|
747
|
-
if (e.key === 'Escape') {
|
|
748
|
-
this.nskdLbrLog('Login cancelled by ESC key', 'info');
|
|
749
|
-
this.closeLoginModal();
|
|
750
|
-
document.removeEventListener('keydown', escHandler);
|
|
751
|
-
reject(new Error('Login cancelled'));
|
|
752
|
-
}
|
|
753
|
-
};
|
|
754
|
-
document.addEventListener('keydown', escHandler);
|
|
755
|
-
|
|
756
|
-
if (certificateValid) {
|
|
757
|
-
passwordInput.focus();
|
|
758
|
-
}
|
|
759
|
-
}
|
|
760
|
-
|
|
761
|
-
/**
|
|
762
|
-
* Perform login API call
|
|
763
|
-
* @private
|
|
764
|
-
*/
|
|
765
|
-
async performLogin(certData, password) {
|
|
766
|
-
const controller = new AbortController();
|
|
767
|
-
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
768
|
-
|
|
769
|
-
try {
|
|
770
|
-
const response = await fetch(this.loginEndpoint, {
|
|
771
|
-
method: 'POST',
|
|
772
|
-
headers: {
|
|
773
|
-
'Content-Type': 'application/json',
|
|
774
|
-
'User-Agent': 'NskdLbr/1.1.0'
|
|
775
|
-
},
|
|
776
|
-
body: JSON.stringify({
|
|
777
|
-
certificate: {
|
|
778
|
-
key: certData.key,
|
|
779
|
-
username: certData.localUsername,
|
|
780
|
-
nickname: certData.nickname,
|
|
781
|
-
certificate_number: certData.certificate_number,
|
|
782
|
-
percentage: certData.percentage,
|
|
783
|
-
country: certData.country,
|
|
784
|
-
countryCode: certData.countryCode,
|
|
785
|
-
creationDate: certData.creationDate
|
|
786
|
-
},
|
|
787
|
-
password: password
|
|
788
|
-
}),
|
|
789
|
-
signal: controller.signal
|
|
790
|
-
});
|
|
791
|
-
|
|
792
|
-
clearTimeout(timeoutId);
|
|
793
|
-
|
|
794
|
-
if (!response.ok) {
|
|
795
|
-
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
796
|
-
}
|
|
797
|
-
|
|
798
|
-
const result = await response.json();
|
|
799
|
-
return result;
|
|
800
|
-
|
|
801
|
-
} catch (error) {
|
|
802
|
-
clearTimeout(timeoutId);
|
|
803
|
-
if (error.name === 'AbortError') {
|
|
804
|
-
throw new Error('Login timeout - server took too long to respond');
|
|
805
|
-
}
|
|
806
|
-
throw new Error(`Login API error: ${error.message}`);
|
|
807
|
-
}
|
|
808
|
-
}
|
|
809
|
-
|
|
810
|
-
/**
|
|
811
|
-
* Close the login modal
|
|
812
|
-
* @private
|
|
813
|
-
*/
|
|
814
|
-
closeLoginModal() {
|
|
815
|
-
if (this.currentLoginModal) {
|
|
816
|
-
this.currentLoginModal.remove();
|
|
817
|
-
this.currentLoginModal = null;
|
|
818
|
-
}
|
|
819
|
-
}
|
|
820
|
-
|
|
821
58
|
/**
|
|
822
59
|
* Load and verify a certificate from a PNG file
|
|
823
60
|
* @param {File} file - The PNG certificate file
|
|
@@ -949,7 +186,6 @@ Certificate Details:
|
|
|
949
186
|
this.verificationKey = null;
|
|
950
187
|
this.localData = null;
|
|
951
188
|
this.isValid = false;
|
|
952
|
-
this.closeLoginModal();
|
|
953
189
|
this.nskdLbrLog('Certificate data reset', 'info');
|
|
954
190
|
}
|
|
955
191
|
|