Subversion Repositories web_pages

Rev

Rev 15 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
12 rodolico 1
<?php
16 rodolico 2
 
14 rodolico 3
/**
4
 * Copyright (c) 2025, Daily Data, Inc.
5
 * All rights reserved.
6
 *
7
 * Redistribution and use in source and binary forms, with or without modification,
8
 * are permitted provided that the following conditions are met:
9
 *
10
 * 1. Redistributions of source code must retain the above copyright notice, this list
11
 *    of conditions and the following disclaimer.
12
 * 2. Redistributions in binary form must reproduce the above copyright notice, this
13
 *    list of conditions and the following disclaimer in the documentation and/or other
14
 *    materials provided with the distribution.
15
 * 3. Neither the name of Daily Data, Inc. nor the names of its contributors may be
16
 *    used to endorse or promote products derived from this software without specific
17
 *    prior written permission.
18
 *
19
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
20
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
21
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
22
 * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
23
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
24
 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
25
 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
27
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
28
 * DAMAGE.
29
 */
12 rodolico 30
 
31
/**
32
 
14 rodolico 33
   PHP script which reads configuration file containing user information. Users
34
   are presented with a username and password box and a router selector.
35
   When processed, will read data file and determine if the credentials match any line
36
   and
37
   * display a QR Code suitable for scanning with one time authentication app
38
   * display the OTP seed code in case user wants to enter it manually
39
   * provide a link to download the OpenVPN configuration file for that user
12 rodolico 40
 
14 rodolico 41
   Assumes configuration file generated by loadOpnSense.pl
42
   Assumes QR code images are pre-generated and stored in a web accessible location
43
   Assumes OpenVPN configuration files are pre-generated and stored in a web accessible location
44
   Assumes passwords are hashed using password_hash function
12 rodolico 45
 
16 rodolico 46
 
14 rodolico 47
   Version 1.0.0 RWR 2025-09-21
48
      Initial Release
49
   Version 1.1.0 RWR 2025-09-27
50
      Added capability of downloading VPN configuration file
51
   Version 1.2.0 RWR 2025-10-01
52
     Reading from json file instead of csv
53
     Removed dependency on exec and external commands
54
     Added logging of successful logins
55
     automatically generate router list from config file
56
     Added some error checking for missing qr code image file and ovpn file
12 rodolico 57
 
58
 */
59
 
16 rodolico 60
   define('VERSION', '1.2.0');
61
 
14 rodolico 62
// this is the only variable you may need to change
63
// everything else is in the configuration file
16 rodolico 64
$configFile = __DIR__ . '/users.json';
13 rodolico 65
 
16 rodolico 66
$isValidUser = false;
67
 
14 rodolico 68
// we need the config data for form processing and validation, so always load it
16 rodolico 69
if (file_exists($configFile)) {
70
    $configData = json_decode(file_get_contents($configFile), true);
14 rodolico 71
} else {
16 rodolico 72
    die("Configuration file $configFile not found");
14 rodolico 73
}
13 rodolico 74
 
12 rodolico 75
// Check if the form is submitted
16 rodolico 76
if ($_SERVER['REQUEST_METHOD'] == 'POST' && isset( $_REQUEST['btnLogin'])) {
77
    // Get the username and password from the form
12 rodolico 78
    $username = $_POST['username'];
79
    $password = $_POST['password'];
80
    $router = $_POST['router'];
81
 
16 rodolico 82
    if ($configData) {
83
        $isValidUser = false;
84
        if (isset($configData[$router]) && isset($configData[$router]['users'][$username])) {
85
            $data = $configData[$router]['users'][$username];
86
            if (password_verify($password, $data['password'])) {
87
                $isValidUser = true;
88
                $code = $data['otp_seed'];
89
                $imageFileName = $data['qrFile'];
90
                $ovpnFileName = $data['ovpnFile'];
91
                $log = date("Y-m-d H:i:s") . "\t" . $_SERVER['REMOTE_ADDR'] . "\t" .
92
                "Success\t" . $username."\t" .PHP_EOL;
93
                file_put_contents('./log_'.date("j.n.Y").'.log', $log, FILE_APPEND);
94
            }
95
        }
96
    } else {
97
        print '<h1>Could not open the configuration file.</h1>';
98
    }
99
} else if ($_SERVER['REQUEST_METHOD'] == 'POST' && isset($_POST['refreshRouter'])) {
100
    $router = preg_replace('/[^A-Za-z0-9_-]/', '', $_POST['refreshRouter']);
101
    $file = "/tmp/update_$router";
102
    file_put_contents($file, time());
103
    exit;
104
}
13 rodolico 105
 
12 rodolico 106
?>
107
 
108
<!DOCTYPE html>
109
<html lang="en">
110
<head>
16 rodolico 111
   <meta charset="UTF-8">
112
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
113
   <title>Get QR Code</title>
12 rodolico 114
</head>
115
<body>
16 rodolico 116
   <h1 style='text-align: center;'>OTP and OVPN configuration download</h1>
117
   <div id='debug'>
118
      <?php
119
         //print "<pre>REQUEST<br/>" . print_r( $_REQUEST, true) . "</pre>";
120
      ?>
121
 
122
   <div id='qr' style='text-align: center;'>
123
      <?php
124
         if ( isset( $_REQUEST['btnLogin']) ) {
125
            if ( $isValidUser ) {
126
               if (empty($code) || empty($imageFileName) || !file_exists($imageFileName)) {
127
                  print "<p><b>QR Code image file not found</b></p>";
128
                  print "<p>You may not be set up with a TOTP code on $router, talk to an administrator</p>";
129
               } else {
130
                  // all good
131
                  print "<img src='$imageFileName' alt='$code'>";
132
                  print "<br />Your code for $router is<br /><b>$code</b>";
133
               }
134
               if (empty($ovpnFileName)) {
135
                  print "<br />No OpenVPN configuration file available";
136
               } else {
137
                  print "<br /><a href='$ovpnFileName' download>Download your OpenVPN Config File</a>";
138
               }
15 rodolico 139
            } else {
16 rodolico 140
               print "<h1>Password wrong, or invalid user $username for router $router</h1>";
15 rodolico 141
            }
16 rodolico 142
         }
143
      ?>
144
   </div>
145
 
146
   <div id='login'>
147
      <form method="POST" action="">
148
         <label for="username">Username:</label>
149
         <input type="text" id="username" name="username" required>
150
         <br>
151
         <label for="password">Password:</label>
152
         <input type="password" id="password" name="password" required>
153
         <br>
154
         <label for="router">Router:</label>
155
            <select name="router" id="router" onchange="updateTimestampAndRefreshLink()">
156
               <?php
157
                  $routers = array_keys($configData);
158
                  foreach ($routers as $index => $name) {
159
                     print "<option value='$name'>$name</option>\n";
160
                  }
161
               ?>
162
            </select>        
163
         <br>
164
         <input type="submit" name="btnLogin" value="Login" id="loginBtn">
165
         <button type="button" id="refreshBtn" style="margin-left:10px;">Request Refresh</button>
166
         <div id="routerTimestamp" style="margin-top:10px;"></div>
167
         <div id="refreshDiv" style="margin-top:10px;"></div>
168
      </form>
169
   </div>
170
 
171
   <script>
172
   const configData = <?php echo json_encode($configData); ?>;
173
   function updateTimestampAndRefreshLink() {
174
      var router = document.getElementById('router').value;
175
      var tsDiv = document.getElementById('routerTimestamp');
176
      if (configData[router] && configData[router]['lastUpdate']) {
177
         var date = new Date(configData[router]['lastUpdate'] * 1000);
178
         tsDiv.innerHTML = '<b>Last update for ' + router + ':</b> ' + date.toLocaleString();
179
      } else {
180
         tsDiv.innerHTML = '<b>No update timestamp available for ' + router + '</b>';
14 rodolico 181
      }
16 rodolico 182
   }
183
   function requestRefresh(router) {
184
      var xhr = new XMLHttpRequest();
185
      xhr.open('POST', '', true);
186
      xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
187
      xhr.send('refreshRouter=' + encodeURIComponent(router));
188
      alert('Refresh requested for ' + router + "\nPlease wait 2 minutes before retrying");
189
   }
190
   document.getElementById('router').addEventListener('change', updateTimestampAndRefreshLink);
191
   window.onload = function() {
192
      updateTimestampAndRefreshLink();
193
      document.getElementById('refreshBtn').onclick = function() {
194
         var router = document.getElementById('router').value;
195
         requestRefresh(router);
196
      };
197
      document.getElementById('loginBtn').focus();
198
   };
199
   document.querySelector('form').addEventListener('keydown', function(e) {
200
      if (e.key === 'Enter') {
201
         e.preventDefault();
202
         document.getElementById('loginBtn').click();
203
      }
204
   });
205
   </script>
206
 
12 rodolico 207
</body>
208
</html>