Subversion Repositories ESP8266_P1_Meter

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
2 raymond 1
#pragma once
2
#include <Arduino.h>
3
 
4
#include <LittleFS.h>
5
#include <AsyncFsWebServer.h>  // https://github.com/cotestatnt/async-esp-fs-webserver
6
#include "mbedtls/md.h"
7
 
8
 
9
// Webserver class
10
AsyncFsWebServer myWebServer(LittleFS, 80, "esp32rfid");
11
 
12
extern TableManager usersTable;
13
extern const char* uniqueKeys[];
14
 
15
 
16
String getSHA256(const char* payload) {
17
  String hashed = "";
18
  byte shaResult[32];
19
  mbedtls_md_context_t ctx;
20
  const size_t payloadLength = strlen(payload);         
21
  mbedtls_md_init(&ctx);
22
  mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), 0);
23
  mbedtls_md_starts(&ctx);
24
  mbedtls_md_update(&ctx, (const unsigned char *) payload, payloadLength);
25
  mbedtls_md_finish(&ctx, shaResult);
26
  mbedtls_md_free(&ctx);
27
  for(int i= 0; i< sizeof(shaResult); i++){
28
    char str[3];
29
    sprintf(str, "%02x", (int)shaResult[i]);
30
    hashed += str;
31
  }
32
  return hashed;
33
}
34
 
35
int getUserLevel(const String& username, const String&  hash) {
36
  JsonObject user = usersTable.findRecord("username", username.c_str());
37
  if (user) {
38
    if (hash.equals(user["password"].as<const char*>())) {
39
      return user["level"].as<int>();
40
    }
41
  }
42
  return 0;
43
}
44
 
45
void handleGetUsers(AsyncWebServerRequest *request) {
46
  JsonArray users = usersTable.getUsers();
47
  String json;
48
  serializeJsonPretty(users, json);
49
  request->send(200, "application/json", json);
50
}
51
 
52
void handleNewUser(AsyncWebServerRequest *request) {
53
  String username = request->arg("username");
54
  String name = request->arg("name");
55
  String email = request->arg("email");
56
  String tag = request->arg("tag");
57
  String level = request->arg("level");
58
  String hashedPassword = getSHA256(request->arg("password").c_str());
59
  bool update = request->arg("type").equals("Update");
60
 
61
  JsonDocument newUser;
62
  newUser["username"] = username;
63
  newUser["password"] = hashedPassword;
64
  newUser["name"] = name;
65
  newUser["email"] = email;
66
  newUser["tag"] = tag;
67
  newUser["level"] = level;
68
 
69
  usersTable.loadTable();
70
 
71
  if (update){
72
    if (!usersTable.deleteRecord("username", username.c_str()))
73
      request->send(500, "text/plain", "Error");
74
  }
75
 
76
  if (usersTable.addRecord(newUser.as<JsonObject>(),uniqueKeys, 2))  {
77
    request->send(200, "text/plain", "OK");
78
    return;
79
  } 
80
  request->send(500, "text/plain", "Error");
81
}
82
 
83
void handleRemoveUser(AsyncWebServerRequest *request) {
84
  String username = request->arg("username");
85
  if (usersTable.deleteRecord("username", username.c_str()))
86
    request->send(200, "text/plain", "OK");
87
  else
88
    request->send(500, "text/plain", "Error");
89
}
90
 
91
void handleGetCode(AsyncWebServerRequest *request) {
92
  uint32_t timeout = millis();
93
 
94
  while (true) {
95
    if (mfrc522.PICC_IsNewCardPresent() && mfrc522.PICC_ReadCardSerial()) {
96
      uint64_t tagCode = 0;
97
 
98
      // tagCode is swapped, but it doesn't matter We need only it's a unique number
99
      for(byte i = 0; i < mfrc522.uid.size; i++) {
100
        tagCode |= mfrc522.uid.uidByte[i] << (8*i);
101
      }
102
 
103
      // With 8 byte TAG code, the result integer could be too large since JavaScript 
104
      // uses 64-bit floating-point numbers (IEEE 754), which have a maximum precision of 2^53 - 1 
105
      String result = "{\"tag\": \"";
106
      result += String(tagCode);  
107
      result += "\"}";
108
 
109
      Serial.printf("Tag code: 0x%llX", tagCode);
110
      request->send(200, "application/json", result);
111
      addLogRecord = true;
112
      return;
113
    }
114
 
115
    if (millis() - timeout > 5000) {
116
      request->send(500, "application/json", "{\"error\": \"timeout\"}");
117
      addLogRecord = true;
118
      return;
119
    }
120
  }
121
}
122
 
123
// This handler will be called from login page to check password
124
void handleCheckHash(AsyncWebServerRequest *request) {
125
 
126
  // Even if user con login, only user with level >= 5 can edit users table
127
  if (getUserLevel(request->arg("username"), request->arg("hash"))) {
128
    request->send(200, "text/plain", "OK");
129
  }
130
  else {
131
    request->send(401, "text/plain", "Wrong password");
132
  }
133
}
134
 
135
 
136
// This handler will be called from login page on login succesfull
137
void handleMainPage(AsyncWebServerRequest *request) {
138
  // Check again user and password to avoid direct page loading
139
  int level = getUserLevel(request->arg("username"), request->arg("hash"));
140
  if (level) {
141
    // Even if any user con login succesfully, only user with level >= 5 can edit users table
142
    // Username and user level is set here using cookie.
143
    String cookie = "username=" ;
144
    cookie += request->arg("username");
145
    cookie += ",";  cookie += level;  cookie += "; Path=/";
146
#if USE_EMBEDDED_HTM
147
    AsyncWebServerResponse *response = request->beginResponse(200, "text/html", (const uint8_t*)rfid, sizeof(rfid));
148
    response->addHeader("Content-Encoding", "gzip");
149
#else
150
    AsyncWebServerResponse *response = request->beginResponse(LittleFS, "/rfid", "text/html");    
151
#endif
152
    response->addHeader("Set-Cookie", cookie);
153
    request->send(response);    
154
  } 
155
  else {
156
    request->send(401, "text/plain", "Wrong password");
157
  }
158
}
159
 
160
 
161
// Configure and start webserver
162
bool startWebServer(bool clear = false) {
163
  bool connected = false;
164
  // FILESYSTEM INIT
165
  if (!LittleFS.begin()) {
166
    Serial.println("ERROR on mounting filesystem. It will be formmatted!");
167
    LittleFS.format();
168
    ESP.restart();
169
  }
170
 
171
  if (clear) {
172
    LittleFS.remove(myWebServer.getConfiFileName());
173
  }
174
 
175
  // Try to connect to WiFi (will start AP if not connected after timeout)
176
  if (!myWebServer.startWiFi(15000)) {
177
    Serial.println("\nWiFi not connected! Starting AP mode...");
178
    myWebServer.startCaptivePortal("ESP32_RFID", "", "/setup");
179
  }
180
  else 
181
    connected = true;
182
 
183
  // Add endpoints request handlers
184
  myWebServer.on("/users", HTTP_GET, handleGetUsers);
185
  myWebServer.on("/addUser", HTTP_POST, handleNewUser);
186
  myWebServer.on("/delUser", HTTP_POST, handleRemoveUser);
187
  myWebServer.on("/getCode", HTTP_GET, handleGetCode);
188
  myWebServer.on("/waitCode", HTTP_GET, [](AsyncWebServerRequest *request){
189
    addLogRecord = false; 
190
    request->send(200, "text/plain", "OK");
191
  });
192
 
193
  /* 
194
  * To avoid ugly and basic login prompt avalaible with "stardard" DIGEST_AUTH
195
  * let's use a custom login web page (from flash literal string). This web page
196
  * will send a POST request to /rfid enpoint passing username and password SHA256 hash
197
  */
198
  myWebServer.on("/", HTTP_ANY, [](AsyncWebServerRequest *request){
199
    request->redirect("/login");
200
  });
201
 
202
  #if USE_EMBEDDED_HTM
203
  myWebServer.on("/login", HTTP_ANY, [](AsyncWebServerRequest *request){
204
    AsyncWebServerResponse *response = request->beginResponse(200, "text/html", (const uint8_t*)login, sizeof(login));
205
    response->addHeader("Content-Encoding", "gzip");
206
    request->send(response);    
207
  });
208
  #else
209
  // Use flash stored file (remember to upload before using /setup embedded webpage)
210
  myWebServer.on("/login", HTTP_ANY, [](AsyncWebServerRequest *request){
211
    AsyncWebServerResponse *response = request->beginResponse(LittleFS, "/login.htm", "text/html");
212
    request->send(response);    
213
  });
214
  #endif
215
 
216
  /*
217
  * If client calculated password SHA256 hash string match with the user stored,
218
  * we can serve the /rfid web page (from flash literal string, same as login page)
219
  */
220
  myWebServer.on("/rfid", HTTP_POST, handleCheckHash);
221
 
222
  /*
223
  * Although it is not the conventional way, handle this page with a PUT request
224
  * otherwise it will be impossible to edit file if needed.
225
  * The embedded /edit webpage uses the GET method to retrieve the file content.
226
  */
227
  myWebServer.on("/rfid", HTTP_PUT, handleMainPage);
228
 
229
  // Enable ACE FS file web editor and add FS info callback function
230
  myWebServer.enableFsCodeEditor();
231
 
232
  // Start the webserver
233
  myWebServer.init();
234
  Serial.print("\n\nESP Web Server started on IP Address: ");
235
  Serial.println(myWebServer.getServerIP());
236
  Serial.println(
237
    "Open /setup page to configure optional parameters.\n"
238
    "Open /edit page to view, edit or upload example or your custom webserver source files."
239
  );
240
 
241
  return connected;
242
}