merge passkeys feature
Bernd Wurst

Bernd Wurst commited on 2023-12-07 11:23:52
Zeige 20 geänderte Dateien mit 982 Einfügungen und 64 Löschungen.

... ...
@@ -111,6 +111,10 @@ function __ensure_connected()
111 111
             if ($debugmode) {
112 112
                 die("MySQL-Fehler: " . $e->getMessage());
113 113
             } else {
114
+                // log errors
115
+                $f = fopen("../dberror.log", "a");
116
+                fwrite($f, date('Y-m-d H:i:s') . ' ' . $_SERVER['PHP_SELF'] . ' DB exception: ' . $e->getMessage() . "\n");
117
+                fclose($f);
114 118
                 die("Fehler bei der Datenbankverbindung!");
115 119
             }
116 120
         }
... ...
@@ -135,6 +139,10 @@ function db_query($stmt, $params = null, $allowempty = false)
135 139
         if ($debugmode) {
136 140
             system_failure("MySQL-Fehler: " . $e->getMessage() . "\nQuery:\n" . $stmt . "\nParameters:\n" . print_r($params, true));
137 141
         } else {
142
+            // log errors
143
+            $f = fopen("../dberror.log", "a");
144
+            fwrite($f, date('Y-m-d H:i:s') . ' ' . $_SERVER['PHP_SELF'] . ' DB exception: ' . $e->getMessage() . "\n");
145
+            fclose($f);
138 146
             system_failure("Datenbankfehler");
139 147
         }
140 148
     }
... ...
@@ -3,6 +3,7 @@
3 3
     "description": "Webinterface running at config.schokokeks.org",
4 4
     "license": "0BSD",
5 5
     "require": {
6
+        "lbuchs/webauthn": "^2.1.0",
6 7
         "bjeavons/zxcvbn-php": "^1.2",
7 8
         "giggsey/libphonenumber-for-php": "^8.8",
8 9
         "globalcitizen/php-iban": "^2.7.1",
... ...
@@ -4,7 +4,7 @@
4 4
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
5 5
         "This file is @generated automatically"
6 6
     ],
7
-    "content-hash": "2ddcdd91f6d49f93b298d93060d37034",
7
+    "content-hash": "b0c98221403e75acf2b0b6fe680d633b",
8 8
     "packages": [
9 9
         {
10 10
             "name": "bjeavons/zxcvbn-php",
... ...
@@ -63,16 +63,16 @@
63 63
         },
64 64
         {
65 65
             "name": "giggsey/libphonenumber-for-php",
66
-            "version": "8.12.43",
66
+            "version": "8.13.26",
67 67
             "source": {
68 68
                 "type": "git",
69 69
                 "url": "https://github.com/giggsey/libphonenumber-for-php.git",
70
-                "reference": "27bc97a4941f42d320fb6da3de0dcaaf7db69f5d"
70
+                "reference": "1d730fe40d5f32641c12ca143a086757c95cfccf"
71 71
             },
72 72
             "dist": {
73 73
                 "type": "zip",
74
-                "url": "https://api.github.com/repos/giggsey/libphonenumber-for-php/zipball/27bc97a4941f42d320fb6da3de0dcaaf7db69f5d",
75
-                "reference": "27bc97a4941f42d320fb6da3de0dcaaf7db69f5d",
74
+                "url": "https://api.github.com/repos/giggsey/libphonenumber-for-php/zipball/1d730fe40d5f32641c12ca143a086757c95cfccf",
75
+                "reference": "1d730fe40d5f32641c12ca143a086757c95cfccf",
76 76
                 "shasum": ""
77 77
             },
78 78
             "require": {
... ...
@@ -128,24 +128,23 @@
128 128
                 "validation"
129 129
             ],
130 130
             "support": {
131
-                "irc": "irc://irc.appliedirc.com/lobby",
132 131
                 "issues": "https://github.com/giggsey/libphonenumber-for-php/issues",
133 132
                 "source": "https://github.com/giggsey/libphonenumber-for-php"
134 133
             },
135
-            "time": "2022-02-09T07:46:55+00:00"
134
+            "time": "2023-11-23T07:57:25+00:00"
136 135
         },
137 136
         {
138 137
             "name": "giggsey/locale",
139
-            "version": "2.1",
138
+            "version": "2.5",
140 139
             "source": {
141 140
                 "type": "git",
142 141
                 "url": "https://github.com/giggsey/Locale.git",
143
-                "reference": "8d324583b5899e6280a875c43bf1fc9658bc6962"
142
+                "reference": "e6d4540109a01dd2bc7334cdc842d6a6a67cf239"
144 143
             },
145 144
             "dist": {
146 145
                 "type": "zip",
147
-                "url": "https://api.github.com/repos/giggsey/Locale/zipball/8d324583b5899e6280a875c43bf1fc9658bc6962",
148
-                "reference": "8d324583b5899e6280a875c43bf1fc9658bc6962",
146
+                "url": "https://api.github.com/repos/giggsey/Locale/zipball/e6d4540109a01dd2bc7334cdc842d6a6a67cf239",
147
+                "reference": "e6d4540109a01dd2bc7334cdc842d6a6a67cf239",
149 148
                 "shasum": ""
150 149
             },
151 150
             "require": {
... ...
@@ -159,10 +158,10 @@
159 158
                 "phing/phing": "^2.7",
160 159
                 "php-coveralls/php-coveralls": "^2.0",
161 160
                 "phpunit/phpunit": "^8.5|^9.5",
162
-                "symfony/console": "^5.0",
163
-                "symfony/filesystem": "^5.0",
164
-                "symfony/finder": "^5.0",
165
-                "symfony/process": "^5.0"
161
+                "symfony/console": "^5.0|^6.0",
162
+                "symfony/filesystem": "^5.0|^6.0",
163
+                "symfony/finder": "^5.0|^6.0",
164
+                "symfony/process": "^5.0|^6.0"
166 165
             },
167 166
             "type": "library",
168 167
             "autoload": {
... ...
@@ -184,9 +183,9 @@
184 183
             "description": "Locale functions required by libphonenumber-for-php",
185 184
             "support": {
186 185
                 "issues": "https://github.com/giggsey/Locale/issues",
187
-                "source": "https://github.com/giggsey/Locale/tree/2.1"
186
+                "source": "https://github.com/giggsey/Locale/tree/2.5"
188 187
             },
189
-            "time": "2021-11-04T19:12:22+00:00"
188
+            "time": "2023-11-01T17:19:48+00:00"
190 189
         },
191 190
         {
192 191
             "name": "globalcitizen/php-iban",
... ...
@@ -220,34 +219,82 @@
220 219
             },
221 220
             "time": "2020-05-03T19:46:35+00:00"
222 221
         },
222
+        {
223
+            "name": "lbuchs/webauthn",
224
+            "version": "v2.1.0",
225
+            "source": {
226
+                "type": "git",
227
+                "url": "https://github.com/lbuchs/WebAuthn.git",
228
+                "reference": "1cc44fbd61d41aec55d0f21f386a1fdd7df1459a"
229
+            },
230
+            "dist": {
231
+                "type": "zip",
232
+                "url": "https://api.github.com/repos/lbuchs/WebAuthn/zipball/1cc44fbd61d41aec55d0f21f386a1fdd7df1459a",
233
+                "reference": "1cc44fbd61d41aec55d0f21f386a1fdd7df1459a",
234
+                "shasum": ""
235
+            },
236
+            "require": {
237
+                "php": ">=8.0.0"
238
+            },
239
+            "type": "library",
240
+            "autoload": {
241
+                "psr-4": {
242
+                    "lbuchs\\WebAuthn\\": "src"
243
+                }
244
+            },
245
+            "notification-url": "https://packagist.org/downloads/",
246
+            "license": [
247
+                "MIT"
248
+            ],
249
+            "authors": [
250
+                {
251
+                    "name": "Lukas Buchs",
252
+                    "role": "Developer"
253
+                }
254
+            ],
255
+            "description": "A simple PHP WebAuthn (FIDO2) server library",
256
+            "homepage": "https://github.com/lbuchs/webauthn",
257
+            "keywords": [
258
+                "Authentication",
259
+                "webauthn"
260
+            ],
261
+            "support": {
262
+                "issues": "https://github.com/lbuchs/WebAuthn/issues",
263
+                "source": "https://github.com/lbuchs/WebAuthn/tree/v2.1.0"
264
+            },
265
+            "time": "2023-10-23T10:19:40+00:00"
266
+        },
223 267
         {
224 268
             "name": "mpdf/mpdf",
225
-            "version": "v8.0.17",
269
+            "version": "v8.2.2",
226 270
             "source": {
227 271
                 "type": "git",
228 272
                 "url": "https://github.com/mpdf/mpdf.git",
229
-                "reference": "5f64118317c8145c0abc606b310aa0a66808398a"
273
+                "reference": "596a87b876d7793be7be060a8ac13424de120dd5"
230 274
             },
231 275
             "dist": {
232 276
                 "type": "zip",
233
-                "url": "https://api.github.com/repos/mpdf/mpdf/zipball/5f64118317c8145c0abc606b310aa0a66808398a",
234
-                "reference": "5f64118317c8145c0abc606b310aa0a66808398a",
277
+                "url": "https://api.github.com/repos/mpdf/mpdf/zipball/596a87b876d7793be7be060a8ac13424de120dd5",
278
+                "reference": "596a87b876d7793be7be060a8ac13424de120dd5",
235 279
                 "shasum": ""
236 280
             },
237 281
             "require": {
238 282
                 "ext-gd": "*",
239 283
                 "ext-mbstring": "*",
284
+                "mpdf/psr-http-message-shim": "^1.0 || ^2.0",
285
+                "mpdf/psr-log-aware-trait": "^2.0 || ^3.0",
240 286
                 "myclabs/deep-copy": "^1.7",
241 287
                 "paragonie/random_compat": "^1.4|^2.0|^9.99.99",
242
-                "php": "^5.6 || ^7.0 || ~8.0.0 || ~8.1.0",
243
-                "psr/log": "^1.0 || ^2.0",
288
+                "php": "^5.6 || ^7.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0",
289
+                "psr/http-message": "^1.0 || ^2.0",
290
+                "psr/log": "^1.0 || ^2.0 || ^3.0",
244 291
                 "setasign/fpdi": "^2.1"
245 292
             },
246 293
             "require-dev": {
247 294
                 "mockery/mockery": "^1.3.0",
248 295
                 "mpdf/qrcode": "^1.1.0",
249 296
                 "squizlabs/php_codesniffer": "^3.5.0",
250
-                "tracy/tracy": "^2.4",
297
+                "tracy/tracy": "~2.5",
251 298
                 "yoast/phpunit-polyfills": "^1.0"
252 299
             },
253 300
             "suggest": {
... ...
@@ -257,6 +304,9 @@
257 304
             },
258 305
             "type": "library",
259 306
             "autoload": {
307
+                "files": [
308
+                    "src/functions.php"
309
+                ],
260 310
                 "psr-4": {
261 311
                     "Mpdf\\": "src/"
262 312
                 }
... ...
@@ -293,32 +343,125 @@
293 343
                     "type": "custom"
294 344
                 }
295 345
             ],
296
-            "time": "2022-01-20T10:51:40+00:00"
346
+            "time": "2023-11-07T13:52:14+00:00"
347
+        },
348
+        {
349
+            "name": "mpdf/psr-http-message-shim",
350
+            "version": "v2.0.1",
351
+            "source": {
352
+                "type": "git",
353
+                "url": "https://github.com/mpdf/psr-http-message-shim.git",
354
+                "reference": "f25a0153d645e234f9db42e5433b16d9b113920f"
355
+            },
356
+            "dist": {
357
+                "type": "zip",
358
+                "url": "https://api.github.com/repos/mpdf/psr-http-message-shim/zipball/f25a0153d645e234f9db42e5433b16d9b113920f",
359
+                "reference": "f25a0153d645e234f9db42e5433b16d9b113920f",
360
+                "shasum": ""
361
+            },
362
+            "require": {
363
+                "psr/http-message": "^2.0"
364
+            },
365
+            "type": "library",
366
+            "autoload": {
367
+                "psr-4": {
368
+                    "Mpdf\\PsrHttpMessageShim\\": "src/"
369
+                }
370
+            },
371
+            "notification-url": "https://packagist.org/downloads/",
372
+            "license": [
373
+                "MIT"
374
+            ],
375
+            "authors": [
376
+                {
377
+                    "name": "Mark Dorison",
378
+                    "email": "mark@chromatichq.com"
379
+                },
380
+                {
381
+                    "name": "Kristofer Widholm",
382
+                    "email": "kristofer@chromatichq.com"
383
+                },
384
+                {
385
+                    "name": "Nigel Cunningham",
386
+                    "email": "nigel.cunningham@technocrat.com.au"
387
+                }
388
+            ],
389
+            "description": "Shim to allow support of different psr/message versions.",
390
+            "support": {
391
+                "issues": "https://github.com/mpdf/psr-http-message-shim/issues",
392
+                "source": "https://github.com/mpdf/psr-http-message-shim/tree/v2.0.1"
393
+            },
394
+            "time": "2023-10-02T14:34:03+00:00"
395
+        },
396
+        {
397
+            "name": "mpdf/psr-log-aware-trait",
398
+            "version": "v3.0.0",
399
+            "source": {
400
+                "type": "git",
401
+                "url": "https://github.com/mpdf/psr-log-aware-trait.git",
402
+                "reference": "a633da6065e946cc491e1c962850344bb0bf3e78"
403
+            },
404
+            "dist": {
405
+                "type": "zip",
406
+                "url": "https://api.github.com/repos/mpdf/psr-log-aware-trait/zipball/a633da6065e946cc491e1c962850344bb0bf3e78",
407
+                "reference": "a633da6065e946cc491e1c962850344bb0bf3e78",
408
+                "shasum": ""
409
+            },
410
+            "require": {
411
+                "psr/log": "^3.0"
412
+            },
413
+            "type": "library",
414
+            "autoload": {
415
+                "psr-4": {
416
+                    "Mpdf\\PsrLogAwareTrait\\": "src/"
417
+                }
418
+            },
419
+            "notification-url": "https://packagist.org/downloads/",
420
+            "license": [
421
+                "MIT"
422
+            ],
423
+            "authors": [
424
+                {
425
+                    "name": "Mark Dorison",
426
+                    "email": "mark@chromatichq.com"
427
+                },
428
+                {
429
+                    "name": "Kristofer Widholm",
430
+                    "email": "kristofer@chromatichq.com"
431
+                }
432
+            ],
433
+            "description": "Trait to allow support of different psr/log versions.",
434
+            "support": {
435
+                "issues": "https://github.com/mpdf/psr-log-aware-trait/issues",
436
+                "source": "https://github.com/mpdf/psr-log-aware-trait/tree/v3.0.0"
437
+            },
438
+            "time": "2023-05-03T06:19:36+00:00"
297 439
         },
298 440
         {
299 441
             "name": "myclabs/deep-copy",
300
-            "version": "1.10.2",
442
+            "version": "1.11.1",
301 443
             "source": {
302 444
                 "type": "git",
303 445
                 "url": "https://github.com/myclabs/DeepCopy.git",
304
-                "reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220"
446
+                "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c"
305 447
             },
306 448
             "dist": {
307 449
                 "type": "zip",
308
-                "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/776f831124e9c62e1a2c601ecc52e776d8bb7220",
309
-                "reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220",
450
+                "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/7284c22080590fb39f2ffa3e9057f10a4ddd0e0c",
451
+                "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c",
310 452
                 "shasum": ""
311 453
             },
312 454
             "require": {
313 455
                 "php": "^7.1 || ^8.0"
314 456
             },
315
-            "replace": {
316
-                "myclabs/deep-copy": "self.version"
457
+            "conflict": {
458
+                "doctrine/collections": "<1.6.8",
459
+                "doctrine/common": "<2.13.3 || >=3,<3.2.2"
317 460
             },
318 461
             "require-dev": {
319
-                "doctrine/collections": "^1.0",
320
-                "doctrine/common": "^2.6",
321
-                "phpunit/phpunit": "^7.1"
462
+                "doctrine/collections": "^1.6.8",
463
+                "doctrine/common": "^2.13.3 || ^3.2.2",
464
+                "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13"
322 465
             },
323 466
             "type": "library",
324 467
             "autoload": {
... ...
@@ -343,7 +486,7 @@
343 486
             ],
344 487
             "support": {
345 488
                 "issues": "https://github.com/myclabs/DeepCopy/issues",
346
-                "source": "https://github.com/myclabs/DeepCopy/tree/1.10.2"
489
+                "source": "https://github.com/myclabs/DeepCopy/tree/1.11.1"
347 490
             },
348 491
             "funding": [
349 492
                 {
... ...
@@ -351,7 +494,7 @@
351 494
                     "type": "tidelift"
352 495
                 }
353 496
             ],
354
-            "time": "2020-11-13T09:40:50+00:00"
497
+            "time": "2023-03-08T13:26:56+00:00"
355 498
         },
356 499
         {
357 500
             "name": "paragonie/random_compat",
... ...
@@ -451,18 +594,71 @@
451 594
             },
452 595
             "time": "2019-03-20T00:55:58+00:00"
453 596
         },
597
+        {
598
+            "name": "psr/http-message",
599
+            "version": "2.0",
600
+            "source": {
601
+                "type": "git",
602
+                "url": "https://github.com/php-fig/http-message.git",
603
+                "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71"
604
+            },
605
+            "dist": {
606
+                "type": "zip",
607
+                "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71",
608
+                "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71",
609
+                "shasum": ""
610
+            },
611
+            "require": {
612
+                "php": "^7.2 || ^8.0"
613
+            },
614
+            "type": "library",
615
+            "extra": {
616
+                "branch-alias": {
617
+                    "dev-master": "2.0.x-dev"
618
+                }
619
+            },
620
+            "autoload": {
621
+                "psr-4": {
622
+                    "Psr\\Http\\Message\\": "src/"
623
+                }
624
+            },
625
+            "notification-url": "https://packagist.org/downloads/",
626
+            "license": [
627
+                "MIT"
628
+            ],
629
+            "authors": [
630
+                {
631
+                    "name": "PHP-FIG",
632
+                    "homepage": "https://www.php-fig.org/"
633
+                }
634
+            ],
635
+            "description": "Common interface for HTTP messages",
636
+            "homepage": "https://github.com/php-fig/http-message",
637
+            "keywords": [
638
+                "http",
639
+                "http-message",
640
+                "psr",
641
+                "psr-7",
642
+                "request",
643
+                "response"
644
+            ],
645
+            "support": {
646
+                "source": "https://github.com/php-fig/http-message/tree/2.0"
647
+            },
648
+            "time": "2023-04-04T09:54:51+00:00"
649
+        },
454 650
         {
455 651
             "name": "psr/log",
456
-            "version": "2.0.0",
652
+            "version": "3.0.0",
457 653
             "source": {
458 654
                 "type": "git",
459 655
                 "url": "https://github.com/php-fig/log.git",
460
-                "reference": "ef29f6d262798707a9edd554e2b82517ef3a9376"
656
+                "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001"
461 657
             },
462 658
             "dist": {
463 659
                 "type": "zip",
464
-                "url": "https://api.github.com/repos/php-fig/log/zipball/ef29f6d262798707a9edd554e2b82517ef3a9376",
465
-                "reference": "ef29f6d262798707a9edd554e2b82517ef3a9376",
660
+                "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001",
661
+                "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001",
466 662
                 "shasum": ""
467 663
             },
468 664
             "require": {
... ...
@@ -471,7 +667,7 @@
471 667
             "type": "library",
472 668
             "extra": {
473 669
                 "branch-alias": {
474
-                    "dev-master": "2.0.x-dev"
670
+                    "dev-master": "3.x-dev"
475 671
                 }
476 672
             },
477 673
             "autoload": {
... ...
@@ -497,22 +693,22 @@
497 693
                 "psr-3"
498 694
             ],
499 695
             "support": {
500
-                "source": "https://github.com/php-fig/log/tree/2.0.0"
696
+                "source": "https://github.com/php-fig/log/tree/3.0.0"
501 697
             },
502
-            "time": "2021-07-14T16:41:46+00:00"
698
+            "time": "2021-07-14T16:46:02+00:00"
503 699
         },
504 700
         {
505 701
             "name": "setasign/fpdi",
506
-            "version": "v2.3.6",
702
+            "version": "v2.5.0",
507 703
             "source": {
508 704
                 "type": "git",
509 705
                 "url": "https://github.com/Setasign/FPDI.git",
510
-                "reference": "6231e315f73e4f62d72b73f3d6d78ff0eed93c31"
706
+                "reference": "ecf0459643ec963febfb9a5d529dcd93656006a4"
511 707
             },
512 708
             "dist": {
513 709
                 "type": "zip",
514
-                "url": "https://api.github.com/repos/Setasign/FPDI/zipball/6231e315f73e4f62d72b73f3d6d78ff0eed93c31",
515
-                "reference": "6231e315f73e4f62d72b73f3d6d78ff0eed93c31",
710
+                "url": "https://api.github.com/repos/Setasign/FPDI/zipball/ecf0459643ec963febfb9a5d529dcd93656006a4",
711
+                "reference": "ecf0459643ec963febfb9a5d529dcd93656006a4",
516 712
                 "shasum": ""
517 713
             },
518 714
             "require": {
... ...
@@ -525,7 +721,7 @@
525 721
             "require-dev": {
526 722
                 "phpunit/phpunit": "~5.7",
527 723
                 "setasign/fpdf": "~1.8",
528
-                "setasign/tfpdf": "1.31",
724
+                "setasign/tfpdf": "~1.31",
529 725
                 "squizlabs/php_codesniffer": "^3.5",
530 726
                 "tecnickcom/tcpdf": "~6.2"
531 727
             },
... ...
@@ -563,7 +759,7 @@
563 759
             ],
564 760
             "support": {
565 761
                 "issues": "https://github.com/Setasign/FPDI/issues",
566
-                "source": "https://github.com/Setasign/FPDI/tree/v2.3.6"
762
+                "source": "https://github.com/Setasign/FPDI/tree/v2.5.0"
567 763
             },
568 764
             "funding": [
569 765
                 {
... ...
@@ -571,20 +767,20 @@
571 767
                     "type": "tidelift"
572 768
                 }
573 769
             ],
574
-            "time": "2021-02-11T11:37:01+00:00"
770
+            "time": "2023-09-28T10:46:27+00:00"
575 771
         },
576 772
         {
577 773
             "name": "symfony/polyfill-mbstring",
578
-            "version": "v1.24.0",
774
+            "version": "v1.28.0",
579 775
             "source": {
580 776
                 "type": "git",
581 777
                 "url": "https://github.com/symfony/polyfill-mbstring.git",
582
-                "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825"
778
+                "reference": "42292d99c55abe617799667f454222c54c60e229"
583 779
             },
584 780
             "dist": {
585 781
                 "type": "zip",
586
-                "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825",
587
-                "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825",
782
+                "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/42292d99c55abe617799667f454222c54c60e229",
783
+                "reference": "42292d99c55abe617799667f454222c54c60e229",
588 784
                 "shasum": ""
589 785
             },
590 786
             "require": {
... ...
@@ -599,7 +795,7 @@
599 795
             "type": "library",
600 796
             "extra": {
601 797
                 "branch-alias": {
602
-                    "dev-main": "1.23-dev"
798
+                    "dev-main": "1.28-dev"
603 799
                 },
604 800
                 "thanks": {
605 801
                     "name": "symfony/polyfill",
... ...
@@ -638,7 +834,7 @@
638 834
                 "shim"
639 835
             ],
640 836
             "support": {
641
-                "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.24.0"
837
+                "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.28.0"
642 838
             },
643 839
             "funding": [
644 840
                 {
... ...
@@ -654,7 +850,7 @@
654 850
                     "type": "tidelift"
655 851
                 }
656 852
             ],
657
-            "time": "2021-11-30T18:21:41+00:00"
853
+            "time": "2023-07-28T09:04:16+00:00"
658 854
         }
659 855
     ],
660 856
     "packages-dev": [],
... ...
@@ -667,5 +863,5 @@
667 863
     "prefer-lowest": false,
668 864
     "platform": [],
669 865
     "platform-dev": [],
670
-    "plugin-api-version": "2.0.0"
866
+    "plugin-api-version": "2.1.0"
671 867
 }
... ...
@@ -25,7 +25,7 @@ $config['db_user'] = 'username';
25 25
 $config['db_pass'] = 'password';
26 26
 
27 27
 
28
-$config['modules'] = ["index", "domains", "imap", "mysql", "jabber", "vhosts", "register", "systemuser", "su"];
28
+$config['modules'] = ["index", "domains", "imap", "mysql", "jabber", "vhosts", "register", "systemuser", "su", "loginsecurity"];
29 29
 
30 30
 $config['enable_debug'] = true;
31 31
 $config['logging'] = LOG_ERR;
... ...
@@ -150,6 +150,11 @@ function login_screen($why = null)
150 150
     if ($why) {
151 151
         warning($why);
152 152
     }
153
+    if (have_module('loginsecurity')) {
154
+        require_once('inc/javascript.php');
155
+        javascript('passkey_ajax.js', 'loginsecurity');
156
+        javascript('passkey_loginpage.js', 'loginsecurity');
157
+    }
153 158
     show_page('login');
154 159
     die();
155 160
 }
... ...
@@ -0,0 +1,35 @@
1
+<?php
2
+/*
3
+This file belongs to the Webinterface of schokokeks.org Hosting
4
+
5
+Written by schokokeks.org Hosting, namely
6
+  Bernd Wurst <bernd@schokokeks.org>
7
+  Hanno Böck <hanno@schokokeks.org>
8
+
9
+This code is published under a 0BSD license.
10
+
11
+Nevertheless, in case you use a significant part of this code, we ask (but not require, see the license) that you keep the authors' names in place and return your changes to the public. We would be especially happy if you tell us what you're going to do with this code.
12
+*/
13
+
14
+require_once('inc/base.php');
15
+require_role(ROLE_SYSTEMUSER);
16
+
17
+require_once('totp.php');
18
+
19
+$id = (int) $_REQUEST['totp'];
20
+
21
+$sure = user_is_sure();
22
+if ($sure === null) {
23
+    $section = 'loginsecurity_overview';
24
+    title("Zwei-Faktor-Anmeldung");
25
+    are_you_sure("totp={$id}", "Möchten Sie die Zwei-Faktor-Anmeldung wirklich entfernen?");
26
+} elseif ($sure === true) {
27
+    delete_systemuser_totp($id);
28
+    if (!$debugmode) {
29
+        header("Location: overview");
30
+    }
31
+} elseif ($sure === false) {
32
+    if (!$debugmode) {
33
+        header("Location: overview");
34
+    }
35
+}
... ...
@@ -0,0 +1,48 @@
1
+<?php
2
+/*
3
+This file belongs to the Webinterface of schokokeks.org Hosting
4
+
5
+Written by schokokeks.org Hosting, namely
6
+  Bernd Wurst <bernd@schokokeks.org>
7
+  Hanno Böck <hanno@schokokeks.org>
8
+
9
+This code is published under a 0BSD license.
10
+
11
+Nevertheless, in case you use a significant part of this code, we ask (but not require, see the license) that you keep the authors' names in place and return your changes to the public. We would be especially happy if you tell us what you're going to do with this code.
12
+*/
13
+
14
+require_once('inc/base.php');
15
+require_role(ROLE_SYSTEMUSER);
16
+
17
+require_once('passkey.php');
18
+
19
+$id = (int) $_REQUEST['id'];
20
+
21
+$pk = null;
22
+$passkeys = list_passkeys();
23
+foreach ($passkeys as $item) {
24
+    if ($item['id'] == $id) {
25
+        $pk = $item;
26
+    }
27
+}
28
+
29
+if (!$pk) {
30
+    system_failure("Invalid passkey");
31
+}
32
+
33
+
34
+$sure = user_is_sure();
35
+if ($sure === null) {
36
+    $section = 'loginsecurity_overview';
37
+    title("Passkey löschen");
38
+    are_you_sure("id={$id}", "Möchten Sie den Passkey #{$pk['id']} ({$pk['handle']}) wirklich entfernen?");
39
+} elseif ($sure === true) {
40
+    delete_systemuser_passkey($id);
41
+    if (!$debugmode) {
42
+        header("Location: overview");
43
+    }
44
+} elseif ($sure === false) {
45
+    if (!$debugmode) {
46
+        header("Location: overview");
47
+    }
48
+}
... ...
@@ -0,0 +1,55 @@
1
+<?php
2
+/*
3
+This file belongs to the Webinterface of schokokeks.org Hosting
4
+
5
+Written by schokokeks.org Hosting, namely
6
+  Bernd Wurst <bernd@schokokeks.org>
7
+  Hanno Böck <hanno@schokokeks.org>
8
+
9
+This code is published under a 0BSD license.
10
+
11
+Nevertheless, in case you use a significant part of this code, we ask (but not require, see the license) that you keep the authors' names in place and return your changes to the public. We would be especially happy if you tell us what you're going to do with this code.
12
+*/
13
+
14
+function save_passkey($data, $handle=null)
15
+{
16
+    require_role(ROLE_SYSTEMUSER);
17
+    $args = [
18
+        ":credentialId" => $data->credentialId,
19
+        ":credentialPublicKey" => $data->credentialPublicKey,
20
+        ":rpId" => $data->rpId,
21
+        ":handle" => $handle,
22
+        ":uid" => $_SESSION['userinfo']['uid']
23
+        ];
24
+    db_query("INSERT INTO system.systemuser_passkey (uid, handle, rpId, credentialId, credentialPublicKey) VALUES ".
25
+            "(:uid, :handle, :rpId, :credentialId, :credentialPublicKey)", $args);
26
+}
27
+
28
+function get_passkey($id) 
29
+{
30
+    $ret = db_query("SELECT uid, handle, credentialPublicKey FROM system.systemuser_passkey WHERE credentialId=:id", [":id" => $id]);
31
+    if ($data = $ret->fetch()) {
32
+        return $data;
33
+    }
34
+    return null;
35
+}
36
+
37
+
38
+function list_passkeys()
39
+{
40
+    $result = db_query("SELECT id, handle, setuptime, rpId FROM system.systemuser_passkey WHERE uid=:uid", [":uid" => $_SESSION['userinfo']['uid']]);
41
+    $ret = [];
42
+    while ($item = $result->fetch()) {
43
+        $ret[] = $item;
44
+    }
45
+    return $ret;
46
+}
47
+
48
+function delete_systemuser_passkey($id) {
49
+    $args = [
50
+        ":id" => $id,
51
+        ":uid" => $_SESSION['userinfo']['uid']
52
+        ];
53
+    db_query("DELETE FROM system.systemuser_passkey WHERE uid=:uid AND id=:id", $args);
54
+}
55
+
... ...
@@ -0,0 +1,189 @@
1
+<?php
2
+/*
3
+This file belongs to the Webinterface of schokokeks.org Hosting
4
+
5
+Written by schokokeks.org Hosting, namely
6
+  Bernd Wurst <bernd@schokokeks.org>
7
+  Hanno Böck <hanno@schokokeks.org>
8
+
9
+This code is published under a 0BSD license.
10
+
11
+Nevertheless, in case you use a significant part of this code, we ask (but not require, see the license) that you keep the authors' names in place and return your changes to the public. We would be especially happy if you tell us what you're going to do with this code.
12
+*/
13
+
14
+require_once('vendor/autoload.php');
15
+
16
+function list_systemuser_totp($uid = null)
17
+{
18
+    if (!$uid) {
19
+        $uid = (int)$_SESSION['userinfo']['uid'];
20
+    }
21
+    $result = db_query("SELECT id, description, setuptime FROM system.systemuser_totp WHERE uid=?", [$uid]);
22
+    $ret = [];
23
+    while ($line = $result->fetch()) {
24
+        $ret[] = $line;
25
+    }
26
+    return $ret;
27
+}
28
+
29
+function check_systemuser_password($password)
30
+{
31
+    $result = db_query("SELECT passwort AS password FROM system.passwoerter WHERE uid=:uid", [":uid" => $_SESSION['userinfo']['uid']]);
32
+    if (@$result->rowCount() > 0) {
33
+        $entry = $result->fetch(PDO::FETCH_OBJ);
34
+        $db_password = $entry->password;
35
+        $hash = crypt($password, $db_password);
36
+        if ($hash == $db_password) {
37
+            return true;
38
+        }
39
+    }
40
+    return false;
41
+}
42
+
43
+
44
+function generate_systemuser_secret()
45
+{
46
+    $ga = new PHPGangsta_GoogleAuthenticator();
47
+
48
+    $secret = $ga->createSecret();
49
+    DEBUG('GA-Secret: ' . $secret);
50
+    return $secret;
51
+}
52
+
53
+function check_systemuser_locked($uid)
54
+{
55
+    if (!$uid) {
56
+        $uid = $_SESSION['userinfo']['uid'];
57
+    }
58
+    $result = db_query("SELECT 1 FROM system.systemuser_totp WHERE unlock_timestamp IS NOT NULL and unlock_timestamp > NOW() AND uid=?", [$uid]);
59
+    return ($result->rowCount() > 0);
60
+}
61
+
62
+function check_systemuser_totp($uid, $code)
63
+{
64
+    $ga = new PHPGangsta_GoogleAuthenticator();
65
+    $secret = null;
66
+    $checkResult = false;
67
+    if (isset($_SESSION['totp_secret'])) {
68
+        // Während des Setup
69
+        $secret = $_SESSION['totp_secret'];
70
+        $checkResult = $ga->verifyCode($secret, $code, 2);    // 2 = 2*30sec clock tolerance
71
+    } else {
72
+        // Normalbetrieb
73
+        if (!$uid) {
74
+            $uid = $_SESSION['userinfo']['uid'];
75
+        }
76
+        $result = db_query("SELECT id,secret,failures FROM system.systemuser_totp WHERE uid=? AND (unlock_timestamp IS NULL OR unlock_timestamp<NOW())", [$uid]);
77
+        while ($tmp = $result->fetch()) {
78
+            $totp_id = $tmp['id'];
79
+            $secret = $tmp['secret'];
80
+        
81
+            if (check_systemuser_blacklist($uid, $totp_id, $code)) {
82
+                DEBUG('Replay-Attack');
83
+                return false;
84
+            }
85
+
86
+            $checkResult = $ga->verifyCode($secret, $code, 2);    // 2 = 2*30sec clock tolerance
87
+            if ($checkResult) {
88
+                db_query("UPDATE system.systemuser_totp SET lastused=CURRENT_TIMESTAMP() WHERE id=?", [$totp_id]);
89
+                blacklist_systemuser_token($uid, $totp_id, $code);
90
+                DEBUG('OK');
91
+            } else {
92
+                DEBUG('TOTP-Code war falsch, checke gegen Restoretoken');
93
+                if ($code == totp_restoretoken($totp_id)) {
94
+                    // Das Restoretoken wird als gültiges OTP anerkannt (eigentlich nicht okay aber einfacher)
95
+                    return true;
96
+                }
97
+                if ($tmp['failures'] > 0 && $tmp['failures'] % 5 == 0) {
98
+                    db_query("UPDATE system.systemuser_totp SET failures = failures+1, unlock_timestamp = NOW() + INTERVAL 5 MINUTE WHERE id=?", [$totp_id]);
99
+                } else {
100
+                    db_query("UPDATE system.systemuser_totp SET failures = failures+1 WHERE id=?", [$totp_id]);
101
+                }
102
+                DEBUG('FAILED');
103
+            }
104
+            if ($checkResult) {
105
+                // Wenn einer stimmt, dann reicht uns das
106
+                return true;
107
+            }
108
+        }
109
+    }
110
+    return $checkResult;
111
+}
112
+
113
+function save_totp_config($description)
114
+{
115
+    if (!isset($_SESSION['totp_secret'])) {
116
+        system_failure("Session kaputt");
117
+    }
118
+    $args = [":uid" => $_SESSION['userinfo']['uid'], ":secret" => $_SESSION['totp_secret'], ":restoretoken" => random_string(30), ":description" => $description];
119
+    db_query("INSERT INTO system.systemuser_totp (description, uid, secret, restoretoken) VALUES (:description, :uid, :secret, :restoretoken)", $args);
120
+    unset($_SESSION['totp_secret']);
121
+    return db_insert_id();
122
+}
123
+
124
+function totp_restoretoken($totp_id)
125
+{
126
+    $result = db_query("SELECT restoretoken FROM system.systemuser_totp WHERE id=:id", 
127
+        [":id" => $totp_id]);
128
+    $data = $result->fetch();
129
+    DEBUG("Restoretoken für #{$totp_id} ist {$data['restoretoken']}");
130
+    return $data['restoretoken'];
131
+}
132
+
133
+function generate_systemuser_qrcode_image($secret)
134
+{
135
+    $username = $_SESSION['userinfo']['username'];
136
+    $url = 'otpauth://totp/'.$username.'@schokokeks.org?secret=' . $secret;
137
+
138
+    $descriptorspec = [
139
+    0 => ["pipe", "r"],  // STDIN ist eine Pipe, von der das Child liest
140
+    1 => ["pipe", "w"],  // STDOUT ist eine Pipe, in die das Child schreibt
141
+    2 => ["pipe", "w"],
142
+  ];
143
+
144
+    $process = proc_open('qrencode -t PNG -s 5 -o -', $descriptorspec, $pipes);
145
+
146
+    if (is_resource($process)) {
147
+        // $pipes sieht nun so aus:
148
+        // 0 => Schreibhandle, das auf das Child STDIN verbunden ist
149
+        // 1 => Lesehandle, das auf das Child STDOUT verbunden ist
150
+
151
+        fwrite($pipes[0], $url);
152
+        fclose($pipes[0]);
153
+
154
+        $pngdata = stream_get_contents($pipes[1]);
155
+        fclose($pipes[1]);
156
+
157
+        // Es ist wichtig, dass Sie alle Pipes schließen bevor Sie
158
+        // proc_close aufrufen, um Deadlocks zu vermeiden
159
+        $return_value = proc_close($process);
160
+
161
+        return $pngdata;
162
+    } else {
163
+        warning('Es ist ein interner Fehler im Webinterface aufgetreten, aufgrund dessen kein QR-Code erstellt werden kann. Sollte dieser Fehler mehrfach auftreten, kontaktieren Sie bitte die Administratoren.');
164
+    }
165
+}
166
+
167
+
168
+function delete_systemuser_totp($id)
169
+{
170
+    $args = [":id" => $id,
171
+                ":uid" => $_SESSION['userinfo']['uid'], ];
172
+
173
+    db_query("DELETE FROM system.systemuser_totp WHERE id=:id AND uid=:uid", $args);
174
+}
175
+
176
+
177
+function blacklist_systemuser_token($uid, $totpid, $token)
178
+{
179
+    $args = [":totpid" => $totpid, ":uid" => $uid, ":token" => $token];
180
+    db_query("INSERT INTO system.systemuser_totp_used (timestamp, uid, totpid, token) VALUES (NOW(), :uid, :totpid, :token)", $args);
181
+}
182
+
183
+function check_systemuser_blacklist($uid, $totpid, $token)
184
+{
185
+    $args = [":totpid" => $totpid, ":uid" => $uid, ":token" => $token];
186
+    db_query("DELETE FROM system.systemuser_totp_used WHERE timestamp < NOW() - INTERVAL 10 MINUTE");
187
+    $result = db_query("SELECT id FROM system.systemuser_totp_used WHERE uid=:uid AND totpid=:totpid AND token=:token", $args);
188
+    return ($result->rowCount() > 0);
189
+}
... ...
@@ -0,0 +1,18 @@
1
+<?php
2
+/*
3
+This file belongs to the Webinterface of schokokeks.org Hosting
4
+
5
+Written by schokokeks.org Hosting, namely
6
+  Bernd Wurst <bernd@schokokeks.org>
7
+  Hanno Böck <hanno@schokokeks.org>
8
+
9
+This code is published under a 0BSD license.
10
+
11
+Nevertheless, in case you use a significant part of this code, we ask (but not require, see the license) that you keep the authors' names in place and return your changes to the public. We would be especially happy if you tell us what you're going to do with this code.
12
+*/
13
+
14
+$role = $_SESSION['role'];
15
+
16
+if ($role & ROLE_SYSTEMUSER) {
17
+    $menu["loginsecurity_overview"] = ["label" => "Sicherheit", "file" => "overview", "weight" => -20, "submenu" => "systemuser_account" ];
18
+}
... ...
@@ -0,0 +1,4 @@
1
+name = totp
2
+description = 2-Faktor-Authentifizierung für Login
3
+permission = Verwalten der 2-Faktor-Authentifizierung
4
+
... ...
@@ -0,0 +1,47 @@
1
+<?php
2
+/*
3
+This file belongs to the Webinterface of schokokeks.org Hosting
4
+
5
+Written by schokokeks.org Hosting, namely
6
+  Bernd Wurst <bernd@schokokeks.org>
7
+  Hanno Böck <hanno@schokokeks.org>
8
+
9
+This code is published under a 0BSD license.
10
+
11
+Nevertheless, in case you use a significant part of this code, we ask (but not require, see the license) that you keep the authors' names in place and return your changes to the public. We would be especially happy if you tell us what you're going to do with this code.
12
+*/
13
+
14
+require_once('passkey.php');
15
+require_once('inc/icons.php');
16
+require_role(ROLE_SYSTEMUSER);
17
+require_once('inc/javascript.php');
18
+javascript("passkey_ajax.js");
19
+
20
+title("Sichere Anmeldung");
21
+
22
+output('<p>Sie können die Anmeldung bei ' . config('company_name') . ' passwortlos einrichten.</p>');
23
+
24
+output('<h3>Anmeldung mit Passkeys / FIDO2</h3>');
25
+
26
+output('<p>Mit der Passkeys-Technologie (technisch identisch zu FIDO2 / WebAuthn) können Sie die Anmeldung mit einem Hardware-Security-Modul oder mit Ihrem Mobilgerät als Schlüssel verwenden (Die Möglichkeiten variieren je nach Betriebssystem bzw. Browser). Wir nutzen in diesem Webinterface den Begriff <em>Passkeys</em>, damit ist jedoch stets auch die Verwendung unterschiedlicher FIDO2-Geräte gemeint.</p>
27
+<p>Bei der Anmeldung mit einem Passkey müssen Sie weder Benutzername noch Passwort eingeben sondern werden (nach Bestätigung Ihres Sicherheitsgeräts) direkt im Webinterface eingeloggt.</p>
28
+<p><strong>Bitte beachten Sie:</strong> Wenn Sie hier einen Schlüssel hinterlegen, funktioniert diese Anmeldemethode nur hier im Webinterface. Für die Anmeldung per SSH müssen Sie für eine vergleichbare Sicherheit und Komfort einen SSH-Schlüssel in Ihrem Benutzeraccount hinterlegen. Je nach Ihrem Client-System kann auch der SSH-Key dann über das Sicherheitsmodul oder via Passkeys verwaltet werden.</p>');
29
+
30
+output('<p>Zur Absicherung Ihres Zugangs empfehlen wir folgendes Vorgehen: Richten Sie den SSH-Zugang über einen SSH-Key und den Zugang zu diesem Webinterface über einen Passkey ein. Setzen Sie dann ein komplexes, neues Passwort zur Wiederherstellung im Fehlerfall und heben Sie dieses Passwort an einem sicheren Ort auf (z.B. ausgedruckt im Tresor). Schalten Sie dann den SSH-Login über Passwort ab. So steht Ihnen das Passwort als Sicherheit zur Wiederherstellung Ihres Zugangs in diesem Webinterface zu Verfügug, es wird aber im Alltag nicht gebraucht.</p>');
31
+
32
+$passkeys = list_passkeys();
33
+if (count($passkeys) > 0) {
34
+    output('<h4>Aktuell eingerichtete Passkeys:</h4>');
35
+    foreach ($passkeys as $pk) {
36
+        $hostname = '';
37
+        $rpId = $_SERVER['HTTP_HOST'];
38
+        if ($pk['rpId'] != $rpId) {
39
+            $hostname = 'Nur gültig für die URL <strong>' . $pk['rpId'] . '</strong>!<br>';
40
+        }
41
+        output("<p class=\"passkey\">Gerätebezeichnung: <strong>{$pk['handle']}</strong><br>hinzugefügt am {$pk['setuptime']}<br>" . $hostname . internal_link("delete_passkey", icon_delete() . "Diesen Passkey löschen", "id={$pk['id']}") . "</p>");
42
+    }
43
+}
44
+
45
+output('<p><label for="passkey_handle">Bezeichnung für neuen Passkey:</label> <input id="passkey_handle" type="text" length="20"> <button onclick="passkey_register()">Passkey registrieren</button></p>
46
+<p><button onclick="passkey_validate(false)">Passkey prüfen</button></p>');
47
+
... ...
@@ -0,0 +1,97 @@
1
+var helper = {
2
+  // (A1) ARRAY BUFFER TO BASE 64
3
+  atb : b => {
4
+    let u = new Uint8Array(b), s = "";
5
+    for (let i=0; i<u.byteLength; i++) { s += String.fromCharCode(u[i]); }
6
+    return btoa(s);
7
+  },
8
+
9
+  // (A2) BASE 64 TO ARRAY BUFFER
10
+  bta : o => {
11
+    let pre = "=?BINARY?B?", suf = "?=";
12
+    for (let k in o) { if (typeof o[k] == "string") {
13
+      let s = o[k];
14
+      if (s.substring(0, pre.length)==pre && s.substring(s.length - suf.length)==suf) {
15
+        let b = window.atob(s.substring(pre.length, s.length - suf.length)),
16
+        u = new Uint8Array(b.length);
17
+        for (let i=0; i<b.length; i++) { u[i] = b.charCodeAt(i); }
18
+        o[k] = u.buffer;
19
+      }
20
+    } else { helper.bta(o[k]); }}
21
+  },
22
+
23
+  // (A3) AJAX FETCH
24
+  ajax : (url, data, after) => {
25
+    let form = new FormData();
26
+    for (let [k,v] of Object.entries(data)) { form.append(k,v); }
27
+    fetch(url, { method: "POST", body: form })
28
+    .then(res => res.text())
29
+    .then(res => after(res))
30
+    .catch(err => { alert("ERROR!"); console.error(err); });
31
+  }
32
+};
33
+
34
+function passkey_register() {
35
+  helper.ajax("../loginsecurity/passkey_ajax", {
36
+    req : "getCreateArgs",
37
+    handle : document.getElementById('passkey_handle') ? document.getElementById('passkey_handle').value : null
38
+  }, async (res) => {
39
+    try {
40
+      res = JSON.parse(res);
41
+      helper.bta(res);
42
+      passkey_register_send(await navigator.credentials.create(res));
43
+    } catch (e) { alert("Error"); console.error(e); }
44
+  });
45
+}
46
+
47
+function passkey_register_send(cred) {
48
+  helper.ajax("../loginsecurity/passkey_ajax", {
49
+    req : "processCreate",
50
+    transport : cred.response.getTransports ? cred.response.getTransports() : null,
51
+    client : cred.response.clientDataJSON ? helper.atb(cred.response.clientDataJSON) : null,
52
+    attest : cred.response.attestationObject ? helper.atb(cred.response.attestationObject) : null
53
+  }, handle_register_result)
54
+}
55
+
56
+function handle_register_result(res) {
57
+    if (res == "OK") {
58
+        // Alles okay
59
+        location.reload();
60
+    } else {
61
+        alert(res);
62
+    }
63
+}
64
+
65
+function passkey_validate(login=false) {
66
+  helper.ajax("../loginsecurity/passkey_ajax", {
67
+    req : "getGetArgs"
68
+  }, async (res) => {
69
+    try {
70
+      res = JSON.parse(res);
71
+      helper.bta(res);
72
+      passkey_validate_send(await navigator.credentials.get(res), login);
73
+    } catch (e) { alert("Error"); console.error(e); }
74
+  });
75
+}
76
+
77
+// (C2) SEND TO SERVER & VALIDATE
78
+function passkey_validate_send(cred, login) {
79
+    helper.ajax("../loginsecurity/passkey_ajax", {
80
+    req : "processGet",
81
+    login : login,
82
+    id : cred.rawId ? helper.atb(cred.rawId) : null,
83
+    client : cred.response.clientDataJSON ? helper.atb(cred.response.clientDataJSON) : null,
84
+    auth : cred.response.authenticatorData ? helper.atb(cred.response.authenticatorData) : null,
85
+    sig : cred.response.signature ? helper.atb(cred.response.signature) : null,
86
+    user : cred.response.userHandle ? helper.atb(cred.response.userHandle) : null
87
+  }, handle_validate_result)
88
+}
89
+
90
+function handle_validate_result(res) {
91
+    if (res == "OK") {
92
+        // Alles okay
93
+        location.reload();
94
+    } else {
95
+        alert(res);
96
+    }
97
+}
... ...
@@ -0,0 +1,113 @@
1
+<?php
2
+/*
3
+This file belongs to the Webinterface of schokokeks.org Hosting
4
+
5
+Written by schokokeks.org Hosting, namely
6
+  Bernd Wurst <bernd@schokokeks.org>
7
+  Hanno Böck <hanno@schokokeks.org>
8
+
9
+This code is published under a 0BSD license.
10
+
11
+Nevertheless, in case you use a significant part of this code, we ask (but not require, see the license) that you keep the authors' names in place and return your changes to the public. We would be especially happy if you tell us what you're going to do with this code.
12
+*/
13
+
14
+require_once('passkey.php');
15
+require_once('inc/base.php');
16
+
17
+require_once('vendor/autoload.php');
18
+
19
+$req = filter_input(INPUT_POST, 'req');
20
+
21
+// Relying Party == Hostname
22
+$rpId = $_SERVER['HTTP_HOST'];
23
+
24
+$WebAuthn = new lbuchs\WebAuthn\WebAuthn(config('company_name').' Webinterface', $rpId);
25
+
26
+if ($req == 'getCreateArgs') {
27
+    require_role(ROLE_SYSTEMUSER);
28
+    $userId = dechex($_SESSION['userinfo']['uid']); // Hex-formatted internal ID not displayed to the user
29
+    if (strlen($userId) % 2 == 1) {
30
+        $userId = "0".$userId;
31
+    }
32
+    $userName = $_SESSION['userinfo']['username'];
33
+    $_SESSION['passkey_handle'] = filter_input(INPUT_POST, "handle");
34
+    $userDisplayName = $_SESSION['userinfo']['name'];
35
+    if ($_SESSION['passkey_handle']) {
36
+        $userDisplayName = $userDisplayName . " ({$_SESSION['passkey_handle']})";
37
+    }
38
+
39
+    $requireResidentKey = 'required';
40
+    $userVerification = 'preferred';
41
+    
42
+    $timeout = 60;
43
+
44
+    $createArgs = $WebAuthn->getCreateArgs(\hex2bin($userId), $userName, $userDisplayName, $timeout, $requireResidentKey, $userVerification);
45
+
46
+    // save challange to session. you have to deliver it to processGet later.
47
+    $_SESSION['challenge'] = ($WebAuthn->getChallenge())->getBinaryString();
48
+
49
+    header('Content-Type: application/json');
50
+    print(json_encode($createArgs));
51
+} elseif ($req == 'processCreate') {
52
+    require_role(ROLE_SYSTEMUSER);
53
+    $client = $_POST['client'];
54
+    $attest = $_POST['attest'];
55
+
56
+    try {
57
+      $data = $WebAuthn->processCreate(
58
+        base64_decode($_POST["client"]),
59
+        base64_decode($_POST["attest"]),
60
+        $_SESSION["challenge"],
61
+        true, true, false, false
62
+      );
63
+    } catch (Exception $ex) { 
64
+        logger(LOG_ERR, "modules/loginsecurity/passkey_ajax", "loginsecurity", "processCreate failed with {$ex}");
65
+        print("Error");
66
+        die();
67
+    }
68
+
69
+    save_passkey($data, $_SESSION['passkey_handle']);
70
+    unset($_SESSION['passkey_handle']);
71
+    print("OK");
72
+    success_msg("Der Passkey wurde gespeichert!");
73
+    die();
74
+} elseif ($req == 'getGetArgs') {
75
+    $args = $WebAuthn->getGetArgs([], 30);
76
+    $_SESSION["challenge"] = ($WebAuthn->getChallenge())->getBinaryString();
77
+    header('Content-Type: application/json');
78
+    print(json_encode($args));
79
+    die();
80
+} elseif ($req == 'processGet') {
81
+    $id = base64_decode($_POST["id"]);
82
+    $savedData = get_passkey($id);
83
+    if (!$savedData) { 
84
+        print("Invalid credentials"); 
85
+        die();
86
+    }
87
+    try {
88
+      $WebAuthn->processGet(
89
+        base64_decode($_POST["client"]),
90
+        base64_decode($_POST["auth"]),
91
+        base64_decode($_POST["sig"]),
92
+        $savedData['credentialPublicKey'],
93
+        $_SESSION["challenge"]
94
+      );
95
+      // DO WHATEVER IS REQUIRED AFTER VALIDATION
96
+      echo "OK";
97
+      $login = ($_POST['login'] == "true");
98
+      if ($login) {
99
+          $uid = $savedData['uid'];
100
+          require_once("session/start.php");
101
+          $role = find_role($uid, '', true);
102
+          setup_session($role, $uid);
103
+          die();
104
+      } else {
105
+          success_msg("Die Identifikation mit dem Passkey »{$savedData['handle']}« hat funktioniert!");
106
+      }
107
+    } catch (Exception $ex) { 
108
+        logger(LOG_ERR, "modules/loginsecurity/passkey_ajax", "loginsecurity", "processGet failed with {$ex}");
109
+        print('Error');
110
+        die();
111
+    }
112
+    die();
113
+}
... ...
@@ -0,0 +1,22 @@
1
+function passkey_login() {
2
+    passkey_validate(true);
3
+}
4
+
5
+
6
+async function check_passkey() {
7
+    if (window.PublicKeyCredential &&  
8
+        PublicKeyCredential.isConditionalMediationAvailable) {
9
+        const newButton = document.createElement('button');
10
+        newButton.textContent = 'Mit Passkey/FIDO2-Gerät anmelden!';
11
+        const newPara = document.createElement('p');
12
+        newPara.appendChild(newButton)
13
+        certlogin = document.getElementById("certlogin")
14
+        certlogin.parentNode.insertBefore(newPara, certlogin)
15
+        newButton.addEventListener('click', passkey_login);
16
+    }
17
+}
18
+
19
+
20
+ready(() => {
21
+    check_passkey();
22
+});
... ...
@@ -0,0 +1,75 @@
1
+<?php
2
+/*
3
+This file belongs to the Webinterface of schokokeks.org Hosting
4
+
5
+Written by schokokeks.org Hosting, namely
6
+  Bernd Wurst <bernd@schokokeks.org>
7
+  Hanno Böck <hanno@schokokeks.org>
8
+
9
+This code is published under a 0BSD license.
10
+
11
+Nevertheless, in case you use a significant part of this code, we ask (but not require, see the license) that you keep the authors' names in place and return your changes to the public. We would be especially happy if you tell us what you're going to do with this code.
12
+*/
13
+
14
+require_once('totp.php');
15
+require_once('inc/base.php');
16
+require_once('inc/security.php');
17
+require_role(ROLE_SYSTEMUSER);
18
+
19
+$username = $_SESSION['userinfo']['username'];
20
+
21
+$section = 'loginsecurity_overview';
22
+title("Zwei-Faktor-Anmeldung für Ihren Account");
23
+
24
+warning('Nach Einrichtung der Zwei-Faktor-Anmeldung benötigen Sie für jeden Login einen Einmal-Code, den Sie mit einem Code-Generator - meist ein Smartphone mit einer entsprechenden App - erzeugen. Dieser Code wird aus einem gemeinsamen Geheimnis und der aktuellen Zeit jeweils neu berechnet.');
25
+output('<p>Zur Einrichtung der Zwei-Faktor-Anmeldung für den Benutzer <strong>'.$username.'</strong>, scannen Sie mit Ihrer Code-Generator-App den unten stehenden QR-Code oder geben Sie das Geheimnis/Secret manuell in den Code-Generator ein.</p>');
26
+
27
+output('<h3>QR-Code für Code-Generator-App</h3>');
28
+output('<p>Der Zugang wird erst dann mit dem zweiten Faktor geschützt, wenn Sie unten einmalig einen korrekten Code eingegeben haben.</p>');
29
+
30
+if (!isset($_SESSION['totp_secret'])) {
31
+    $_SESSION['totp_secret'] = generate_systemuser_secret();
32
+}
33
+
34
+$qrcode_image = generate_systemuser_qrcode_image($_SESSION['totp_secret']);
35
+output('<p><img src="data:image/png;base64,' . base64_encode($qrcode_image) . '" /></p>
36
+<p>Secret-Code für manuelle initialisierung des Code-Generators: <span style="font-size: 120%;">' . $_SESSION['totp_secret'] . '</span></p>');
37
+
38
+
39
+$passed = false;
40
+$totp_id = null;
41
+if (isset($_POST['password']) && isset($_POST['token'])) {
42
+    // Prüfen, ob das Passwort und der Code stimmen
43
+    if (! check_systemuser_password($_POST['password'])) {
44
+        input_error('Das Passwort scheint falsch zu sein.');
45
+        $passed=false;
46
+    }
47
+    if (check_systemuser_totp($_SESSION['userinfo']['uid'], $_POST['token'])) {
48
+        // Passwort stimmt, Token stimmt
49
+        // Config abspeichern
50
+        $description = null;
51
+        if (isset($_POST['description'])) {
52
+            $description = $_POST['description'];
53
+        }
54
+        $totp_id = save_totp_config($description);
55
+        $passed = true;
56
+    } else {
57
+        input_error('Der Code hat nicht gestimmt. Bitte prüfen Sie auch, ob die Egeräte-Uhrzeit stimmt.');
58
+        $passed = false;
59
+    }
60
+}
61
+
62
+
63
+if ($passed) {
64
+    output('<p>Der obige Code wurde als zweiter Faktor eingerichtet!</p><p>Bitte notieren Sie sich den folgenden Restore-Code für den Fall, dass der Code-Generator nicht mehr verfügbar ist (Beschädigung am Handy, ...).</p><p>Restore-Code: <span style="font-size: 120%;">' . totp_restoretoken($totp_id) . '</span></p>');
65
+} else {
66
+
67
+    $form = '<p>Geben Sie zur Identifikation bitte Ihr Passwort und den aktuell vom Generator erzeugten Code ein.</p>
68
+<p>Passwort: <input type="password" name="password" /></p>
69
+<p>Bezeichnung für diesen Code-Generator: <input type="text" name="description" /></p>
70
+<p>Aktueller TOTP-Code: <input type="text" name="token" /></p>';
71
+
72
+    $form .= '<p><input type="submit" value="Einrichten" /></p>';
73
+
74
+    output(html_form('totp_setup', 'setup', '', $form));
75
+}
... ...
@@ -0,0 +1,5 @@
1
+.passkey {
2
+    border: 1px solid black;
3
+    border-radius: 5px;
4
+    padding: 5px;
5
+}
... ...
@@ -14,7 +14,7 @@ Nevertheless, in case you use a significant part of this code, we ask (but not r
14 14
 require_once('inc/base.php');
15 15
 require_once('inc/debug.php');
16 16
 require_once('inc/error.php');
17
-
17
+require_once('modules/loginsecurity/include/totp.php');
18 18
 
19 19
 define('ROLE_ANONYMOUS', 0);
20 20
 define('ROLE_MAILACCOUNT', 1);
... ...
@@ -43,7 +43,7 @@ function find_role($login, $password, $i_am_admin = false)
43 43
     if ($uid == 0) {
44 44
         $uid = null;
45 45
     }
46
-    $result = db_query("SELECT username, passwort AS password, kundenaccount AS `primary`, status, ((SELECT acc.uid FROM system.v_useraccounts AS acc LEFT JOIN system.gruppenzugehoerigkeit USING (uid) LEFT JOIN system.gruppen AS g ON (g.gid=gruppenzugehoerigkeit.gid) WHERE g.name='admin' AND acc.uid=u.uid) IS NOT NULL) AS admin FROM system.v_useraccounts AS u LEFT JOIN system.passwoerter USING(uid) WHERE u.uid=:uid OR username=:login LIMIT 1;", [":uid" => $uid, ":login" => $login]);
46
+    $result = db_query("SELECT uid, username, passwort AS password, kundenaccount AS `primary`, status, ((SELECT acc.uid FROM system.v_useraccounts AS acc LEFT JOIN system.gruppenzugehoerigkeit USING (uid) LEFT JOIN system.gruppen AS g ON (g.gid=gruppenzugehoerigkeit.gid) WHERE g.name='admin' AND acc.uid=u.uid) IS NOT NULL) AS admin FROM system.v_useraccounts AS u LEFT JOIN system.passwoerter USING(uid) WHERE u.uid=:uid OR username=:login LIMIT 1;", [":uid" => $uid, ":login" => $login]);
47 47
     if (@$result->rowCount() > 0) {
48 48
         $entry = $result->fetch(PDO::FETCH_OBJ);
49 49
         if (strcasecmp($entry->username, $login) == 0 && $entry->username != $login) {
... ...
@@ -107,7 +107,7 @@ function find_role($login, $password, $i_am_admin = false)
107 107
             if (check_webmail_password($account, $password)) {
108 108
                 $_SESSION['totp_username'] = $account;
109 109
                 $_SESSION['totp'] = true;
110
-                show_page('webmailtotp-login');
110
+                show_page('totp-login');
111 111
                 die();
112 112
             } else {
113 113
                 return null;
... ...
@@ -63,7 +63,7 @@ if ($messages) {
63 63
 <p>Sie können sich hier mit Ihrem System-Benutzernamen, Ihrer E-Mail-Adresse oder Ihrer Kundennummer (jeweils mit zugehörigem Passwort) anmelden. Je nach gewählten Daten erhalten Sie unterschiedliche Zugriffsrechte.</p>
64 64
 <?php /* <p>Sollten Sie Ihr Benutzer-Passwort nicht mehr kennen, wenden Sie sich bitte an den Support. Passwörter für E-Mail-Konten kann der Eigentümer des Benutzeraccounts neu setzen.</p> */ ?>
65 65
 
66
-<p><em><a href="../../certlogin/?destination=go/<?php echo $go; ?>"  >Mit einem Client-Zertifikat anmelden</a></em> (<a href="../../go/index/certinfo"  >Wie geht das?</a>)</p>
66
+<p id="certlogin"><em><a href="../../certlogin/?destination=go/<?php echo $go; ?>"  >Mit einem Client-Zertifikat anmelden</a></em> (<a href="../../go/index/certinfo"  >Wie geht das?</a>)</p>
67 67
 
68 68
 
69 69
 <?php if ($footnotes) {
... ...
@@ -55,7 +55,7 @@ if ($messages) {
55 55
 <h3 class="headline">Sicherheits-Code</h3>
56 56
 <p>Ihr Zugang ist mit Zwei-Faktor-Anmeldung geschützt. Sie müssen daher jetzt noch den aktuellsten Code Ihres TOTP-Geräts eingeben.</p>
57 57
 <form method="post">
58
-<p><label for="code" class="login_label">Google-Authenticator-Code:</label> <input type="text" id="code" name="webinterface_totpcode" size="20" /></p>
58
+<p><label for="code" class="login_label">TOTP-Authenticator-Code:</label> <input type="text" id="code" name="webinterface_totpcode" size="20" /></p>
59 59
 <p><span class="login_label">&#160;</span> <input type="submit" value="Prüfen" /></p>
60 60
 </form>
61 61
 
62 62