52 {
55
58 .username = "rathalin",
59 .password = "rathalin",
60 .database = "oauth2",
61 .port = 3306,
62 .socket = "/run/mysqld/mysqld.sock",
63 .flags = 0,
64 };
66
68
70
71
73 const std::string queryClientId{req->query("client_id")};
74 if (queryClientId.length() > 0) {
75 db.query(
76 "select count(*) from client where uuid = '" + queryClientId + "'",
77 [req, res, next, queryClientId](const MYSQL_ROW row) {
78 if (row != nullptr) {
79 if (std::stoi(row[0]) > 0) {
80 VLOG(1) << "Valid client id '" << queryClientId << "'";
81 VLOG(1) << "Next with " << req->httpVersion << " " << req->method << " " << req->url;
82 next();
83 } else {
84 VLOG(1) << "Invalid client id '" << queryClientId << "'";
85 res->sendStatus(401);
86 }
87 }
88 },
89 [res](const std::string& errorString, unsigned int errorNumber) {
90 VLOG(1) << "Database error: " << errorString << " : " << errorNumber;
91 res->sendStatus(500);
92 });
93 } else {
94 res->status(401).send("Invalid client_id");
95 }
96 });
97
99
100
101
102 const std::string paramResponseType{req->query("response_type")};
103 const std::string paramClientId{req->query("client_id")};
104 const std::string paramRedirectUri{req->query("redirect_uri")};
105 const std::string paramScope{req->query("scope")};
106 const std::string paramState{req->query("state")};
107
108 VLOG(1) << "Query params: "
109 << "response_type=" << req->query("response_type") << ", "
110 << "redirect_uri=" << req->query("redirect_uri") << ", "
111 << "scope=" << req->query("scope") << ", "
112 << "state=" << req->query("state") << "\n";
113
114 if (paramResponseType != "code") {
115 VLOG(1) << "Auth invalid, sending Bad Request";
116 res->sendStatus(400);
117 return;
118 }
119
120 if (!paramRedirectUri.empty()) {
121 db.exec(
122 "update client set redirect_uri = '" + paramRedirectUri + "' where uuid = '" + paramClientId + "'",
123 [paramRedirectUri]() {
124 VLOG(1) << "Database: Set redirect_uri to " << paramRedirectUri;
125 },
126 [](const std::string& errorString, unsigned int errorNumber) {
127 VLOG(1) << "Database error: " << errorString << " : " << errorNumber;
128 });
129 }
130
131 if (!paramScope.empty()) {
132 db.exec(
133 "update client set scope = '" + paramScope + "' where uuid = '" + paramClientId + "'",
134 [paramScope]() {
135 VLOG(1) << "Database: Set scope to " << paramScope;
136 },
137 [](const std::string& errorString, unsigned int errorNumber) {
138 VLOG(1) << "Database error: " << errorString << " : " << errorNumber;
139 });
140 }
141
142 if (!paramState.empty()) {
143 db.exec(
144 "update client set state = '" + paramState + "' where uuid = '" + paramClientId + "'",
145 [paramState]() {
146 VLOG(1) << "Database: Set state to " << paramState;
147 },
148 [](const std::string& errorString, unsigned int errorNumber) {
149 VLOG(1) << "Database error: " << errorString << " : " << errorNumber;
150 });
151 }
152
153 VLOG(1) << "Auth request valid, redirecting to login";
154 std::string loginUri{"/oauth2/login"};
156 res->redirect(loginUri);
157 });
158
160 res->sendFile("/home/rathalin/projects/snode.c/src/oauth2/authorization_server/vue-frontend-oauth2-auth-server/dist/index.html",
161 [req](int ret) {
162 if (ret != 0) {
163 PLOG(ERROR) << req->url;
164 }
165 });
166 });
167
169 req->getAttribute<nlohmann::json>(
170 [req, res, &db](nlohmann::json& body) {
171 db.query(
172 "select email, password_hash, password_salt, redirect_uri, state "
173 "from client "
174 "where uuid = '" +
175 req->query("client_id") + "'",
176 [req, res, &db, &body](const MYSQL_ROW row) {
177 if (row != nullptr) {
178 const std::string dbEmail{row[0]};
179 const std::string dbPasswordHash{row[1]};
180 const std::string dbPasswordSalt{row[2]};
181 const std::string dbRedirectUri{row[3]};
182 const std::string dbState{row[4]};
183 const std::string queryEmail{body["email"]};
184 const std::string queryPassword{body["password"]};
185
186 if (dbEmail != queryEmail) {
187 res->status(401).send("Invalid email address");
188 }
else if (dbPasswordHash !=
hashSha1(dbPasswordSalt + queryPassword)) {
189 res->status(401).send("Invalid password");
190 } else {
191
192 const unsigned int expireMinutes{10};
194 db.exec(
195 "insert into token(uuid, expire_datetime) "
196 "values('" +
197 authCode + "', '" +
198 timeToString(std::chrono::system_clock::now() + std::chrono::minutes(expireMinutes)) +
"')",
199 []() {
200 },
201 [res](const std::string& errorString, unsigned int errorNumber) {
202 VLOG(1) << "Database error: " << errorString << " : " << errorNumber;
203 res->sendStatus(500);
204 })
205 .query(
206 "select last_insert_id()",
207 [req, res, &db, dbState, dbRedirectUri, authCode](const MYSQL_ROW row) {
208 if (row != nullptr) {
209 db.exec(
210 "update client "
211 "set auth_code_id = '" +
212 std::string{row[0]} +
213 "' "
214 "where uuid = '" +
215 req->query("client_id") + "'",
216 [res, dbState, dbRedirectUri, authCode]() {
217
218 std::string clientRedirectUri{dbRedirectUri};
220 if (!dbState.empty()) {
222 }
223
224 res->set("Access-Control-Allow-Origin", "*");
225 const nlohmann::json responseJson = {{"redirect_uri", clientRedirectUri}};
226 const std::string responseJsonString{responseJson.dump(4)};
227 VLOG(1) << "Sending json reponse: " << responseJsonString;
228 res->send(responseJsonString);
229 },
230 [res](const std::string& errorString, unsigned int errorNumber) {
231 VLOG(1) << "Database error: " << errorString << " : " << errorNumber;
232 res->sendStatus(500);
233 });
234 }
235 },
236 [res](const std::string& errorString, unsigned int errorNumber) {
237 VLOG(1) << "Database error: " << errorString << " : " << errorNumber;
238 res->sendStatus(500);
239 });
240 }
241 }
242 },
243 [res](const std::string& errorString, unsigned int errorNumber) {
244 VLOG(1) << "Database error: " << errorString << " : " << errorNumber;
245 res->sendStatus(500);
246 });
247 },
248 [res]([[maybe_unused]] const std::string& key) {
249 res->sendStatus(500);
250 });
251 });
252
254 res->set("Access-Control-Allow-Origin", "*");
255 auto queryGrantType = req->query("grant_type");
256 VLOG(1) << "GrandType: " << queryGrantType;
257 auto queryCode = req->query("code");
258 VLOG(1) << "Code: " << queryCode;
259 auto queryRedirectUri = req->query("redirect_uri");
260 VLOG(1) << "RedirectUri: " << queryRedirectUri;
261 if (queryGrantType != "authorization_code") {
262 res->status(400).send("Invalid query parameter 'grant_type', value must be 'authorization_code'");
263 return;
264 }
265 if (queryCode.length() == 0) {
266 res->status(400).send("Missing query parameter 'code'");
267 return;
268 }
269 if (queryRedirectUri.length() == 0) {
270 res->status(400).send("Missing query parameter 'redirect_uri'");
271 return;
272 }
273 db.query(
274 "select count(*) "
275 "from client "
276 "where uuid = '" +
277 req->query("client_id") +
278 "' "
279 "and redirect_uri = '" +
280 queryRedirectUri + "'",
281 [req, res, &db](const MYSQL_ROW row) {
282 if (row != nullptr) {
283 if (std::stoi(row[0]) == 0) {
284 res->status(400).send("Query param 'redirect_uri' must be the same as in the initial request");
285 } else {
286 db.query(
287 "select count(*) "
288 "from client c "
289 "join token a "
290 "on c.auth_code_id = a.id "
291 "where c.uuid = '" +
292 req->query("client_id") +
293 "' "
294 "and a.uuid = '" +
295 req->query("code") +
296 "' "
297 "and timestampdiff(second, current_timestamp(), a.expire_datetime) > 0",
298 [req, res, &db](const MYSQL_ROW row) {
299 if (row != nullptr) {
300 if (std::stoi(row[0]) == 0) {
301 res->status(401).send("Invalid auth token");
302 return;
303 }
304
305 const std::string accessToken{getNewUUID()};
306 const unsigned int accessTokenExpireSeconds{60 * 60};
307 const std::string refreshToken{getNewUUID()};
308 const unsigned int refreshTokenExpireSeconds{60 * 60 * 24};
309 db.exec(
310 "insert into token(uuid, expire_datetime) "
311 "values('" +
312 accessToken + "', '" +
313 timeToString(std::chrono::system_clock::now() +
314 std::chrono::seconds(accessTokenExpireSeconds)) +
315 "')",
316 []() {
317 },
318 [res](const std::string& errorString, unsigned int errorNumber) {
319 VLOG(1) << "Database error: " << errorString << " : " << errorNumber;
320 res->sendStatus(500);
321 })
322 .query(
323 "select last_insert_id()",
324 [req, res, &db](const MYSQL_ROW row) {
325 if (row != nullptr) {
326 db.exec(
327 "update client "
328 "set access_token_id = '" +
329 std::string{row[0]} +
330 "' "
331 "where uuid = '" +
332 req->query("client_id") + "'",
333 []() {
334 },
335 [res](const std::string& errorString, unsigned int errorNumber) {
336 VLOG(1) << "Database error: " << errorString << " : " << errorNumber;
337 res->sendStatus(500);
338 });
339 }
340 },
341 [res](const std::string& errorString, unsigned int errorNumber) {
342 VLOG(1) << "Database error: " << errorString << " : " << errorNumber;
343 res->sendStatus(500);
344 })
345 .exec(
346 "insert into token(uuid, expire_datetime) "
347 "values('" +
348 refreshToken + "', '" +
349 timeToString(std::chrono::system_clock::now() +
350 std::chrono::seconds(refreshTokenExpireSeconds)) +
351 "')",
352 []() {
353 },
354 [res](const std::string& errorString, unsigned int errorNumber) {
355 VLOG(1) << "Database error: " << errorString << " : " << errorNumber;
356 res->sendStatus(500);
357 })
358 .query(
359 "select last_insert_id()",
360 [req, res, &db, accessToken, accessTokenExpireSeconds, refreshToken](const MYSQL_ROW row) {
361 if (row != nullptr) {
362 db.exec(
363 "update client "
364 "set refresh_token_id = '" +
365 std::string{row[0]} +
366 "' "
367 "where uuid = '" +
368 req->query("client_id") + "'",
369 [res, accessToken, accessTokenExpireSeconds, refreshToken]() {
370
371 const nlohmann::json jsonResponse = {{"access_token", accessToken},
372 {"expires_in", accessTokenExpireSeconds},
373 {"refresh_token", refreshToken}};
374 const std::string jsonResponseString{jsonResponse.dump(4)};
375 res->send(jsonResponseString);
376 },
377 [res](const std::string& errorString, unsigned int errorNumber) {
378 VLOG(1) << "Database error: " << errorString << " : " << errorNumber;
379 res->sendStatus(500);
380 });
381 }
382 },
383 [res](const std::string& errorString, unsigned int errorNumber) {
384 VLOG(1) << "Database error: " << errorString << " : " << errorNumber;
385 res->sendStatus(500);
386 });
387 }
388 },
389 [res](const std::string& errorString, unsigned int errorNumber) {
390 VLOG(1) << "Database error: " << errorString << " : " << errorNumber;
391 res->sendStatus(500);
392 });
393 }
394 }
395 },
396 [res](const std::string& errorString, unsigned int errorNumber) {
397 VLOG(1) << "Database error: " << errorString << " : " << errorNumber;
398 res->sendStatus(500);
399 });
400 });
401
403 res->set("Access-Control-Allow-Origin", "*");
404 auto queryClientId = req->query("client_id");
405 VLOG(1) << "ClientId: " << queryClientId;
406 auto queryGrantType = req->query("grant_type");
407 VLOG(1) << "GrandType: " << queryGrantType;
408 auto queryRefreshToken = req->query("refresh_token");
409 VLOG(1) << "RefreshToken: " << queryRefreshToken;
410 auto queryState = req->query("state");
411 VLOG(1) << "State: " << queryState;
412 if (queryGrantType.length() == 0) {
413 res->status(400).send("Missing query parameter 'grant_type'");
414 return;
415 }
416 if (queryGrantType != "refresh_token") {
417 res->status(400).send("Invalid query parameter 'grant_type', value must be 'refresh_token'");
418 return;
419 }
420 if (queryRefreshToken.empty()) {
421 res->status(400).send("Missing query parameter 'refresh_token'");
422 }
423 db.query(
424 "select count(*) "
425 "from client c "
426 "join token r "
427 "on c.refresh_token_id = r.id "
428 "where c.uuid = '" +
429 req->query("client_id") +
430 "' "
431 "and r.uuid = '" +
432 req->query("refresh_token") +
433 "' "
434 "and timestampdiff(second, current_timestamp(), r.expire_datetime) > 0",
435 [req, res, &db](const MYSQL_ROW row) {
436 if (row != nullptr) {
437 if (std::stoi(row[0]) == 0) {
438 res->status(401).send("Invalid refresh token");
439 return;
440 }
441
442 std::string accessToken{getNewUUID()};
443 unsigned int accessTokenExpireSeconds{60 * 60};
444 db.exec(
445 "insert into token(uuid, expire_datetime) "
446 "values('" +
447 accessToken + "', '" +
448 timeToString(std::chrono::system_clock::now() + std::chrono::seconds(accessTokenExpireSeconds)) + "')",
449 []() {
450 },
451 [res](const std::string& errorString, unsigned int errorNumber) {
452 VLOG(1) << "Database error: " << errorString << " : " << errorNumber;
453 res->sendStatus(500);
454 })
455 .query(
456 "select last_insert_id()",
457 [req, res, &db, accessToken, accessTokenExpireSeconds](const MYSQL_ROW row) {
458 if (row != nullptr) {
459 db.exec(
460 "update client "
461 "set access_token_id = '" +
462 std::string{row[0]} +
463 "' "
464 "where uuid = '" +
465 req->query("client_id") + "'",
466 [res, accessToken, accessTokenExpireSeconds]() {
467 const nlohmann::json responseJson = {{"access_token", accessToken},
468 {"expires_in", accessTokenExpireSeconds}};
469 res->send(responseJson.dump(4));
470 },
471 [res](const std::string& errorString, unsigned int errorNumber) {
472 VLOG(1) << "Database error: " << errorString << " : " << errorNumber;
473 res->sendStatus(500);
474 });
475 }
476 },
477 [res](const std::string& errorString, unsigned int errorNumber) {
478 VLOG(1) << "Database error: " << errorString << " : " << errorNumber;
479 res->sendStatus(500);
480 });
481 }
482 },
483 [res](const std::string& errorString, unsigned int errorNumber) {
484 VLOG(1) << "Database error: " << errorString << " : " << errorNumber;
485 res->sendStatus(500);
486 });
487 });
488
490 VLOG(1) << "POST /token/validate";
491 req->getAttribute<nlohmann::json>([res, &db](nlohmann::json& jsonBody) {
492 if (!jsonBody.contains("access_token")) {
493 VLOG(1) << "Missing 'access_token' in json";
494 res->status(500).send("Missing 'access_token' in json");
495 return;
496 }
497 const std::string jsonAccessToken{jsonBody["access_token"]};
498 if (!jsonBody.contains("client_id")) {
499 VLOG(1) << "Missing 'client_id' in json";
500 res->status(500).send("Missing 'client_id' in json");
501 return;
502 }
503 const std::string jsonClientId{jsonBody["client_id"]};
504 db.query(
505 "select count(*) "
506 "from client c "
507 "join token a "
508 "on c.access_token_id = a.id "
509 "where c.uuid = '" +
510 jsonClientId +
511 "' "
512 "and a.uuid = '" +
513 jsonAccessToken + "'",
514 [res, jsonClientId, jsonAccessToken](const MYSQL_ROW row) {
515 if (row != nullptr) {
516 if (std::stoi(row[0]) == 0) {
517 const nlohmann::json errorJson = {{"error", "Invalid access token"}};
518 VLOG(1) << "Sending 401: Invalid access token '" << jsonAccessToken << "'";
519 res->status(401).send(errorJson.dump(4));
520 } else {
521 VLOG(1) << "Sending 200: Valid access token '" << jsonAccessToken << "";
522 const nlohmann::json successJson = {{"success", "Valid access token"}};
523 res->status(200).send(successJson.dump(4));
524 }
525 }
526 },
527 [res](const std::string& errorString, unsigned int errorNumber) {
528 VLOG(1) << "Database error: " << errorString << " : " << errorNumber;
529 res->sendStatus(500);
530 });
531 });
532 });
533
536 "/home/rathalin/projects/snode.c/src/oauth2/authorization_server/vue-frontend-oauth2-auth-server/dist/"));
537
539 switch (state) {
541 VLOG(1) << "OAuth2AuthorizationServer: listening on '" << socketAddress.toString() << "'";
542 break;
544 VLOG(1) << "OAuth2AuthorizationServer: disabled";
545 break;
547 VLOG(1) << "OAuth2AuthorizationServer: error occurred";
548 break;
550 VLOG(1) << "OAuth2AuthorizationServer: fatal error occurred";
551 break;
552 }
553 });
554
556}
std::string timeToString(std::chrono::time_point< std::chrono::system_clock > time)
void addQueryParamToUri(std::string &uri, const std::string &queryParamName, const std::string &queryParamValue)
std::string hashSha1(const std::string &str)
#define APPLICATION(req, res)
#define MIDDLEWARE(req, res, next)
static constexpr int DISABLED
static constexpr int ERROR
static constexpr int FATAL
Route & use(const std::function< void(const std::shared_ptr< Request > &, const std::shared_ptr< Response > &)> &lambda) const
Route & get(const std::function< void(const std::shared_ptr< Request > &, const std::shared_ptr< Response > &)> &lambda) const
Route & post(const std::function< void(const std::shared_ptr< Request > &, const std::shared_ptr< Response > &)> &lambda) const
Route & use(const Router &router) const
typename Server::SocketAddress SocketAddress
static void init(int argc, char *argv[])
static int start(const utils::Timeval &timeOut={LONG_MAX, 0})
Router router(database::mariadb::MariaDBClient &db)