2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
42#include "database/mariadb/MariaDBClient.h"
43#include "database/mariadb/MariaDBCommandSequence.h"
44#include "express/legacy/in/WebApp.h"
45#include "express/middleware/JsonMiddleware.h"
46#include "express/middleware/StaticMiddleware.h"
47#include "log/Logger.h"
48#include "utils/sha1.h"
55#include <nlohmann/json.hpp>
62void addQueryParamToUri(std::string& uri,
const std::string& queryParamName,
const std::string& queryParamValue) {
63 if (uri.find(
'?') == std::string::npos) {
68 uri += queryParamName +
"=" + queryParamValue;
72 auto in_time_t = std::chrono::system_clock::to_time_t(time);
74 ss << std::put_time(std::localtime(&in_time_t),
"%Y-%m-%d %X");
79 const size_t uuidLength{36};
80 char uuidCharArray[uuidLength];
81 std::ifstream file(
"/proc/sys/kernel/random/uuid");
82 file.getline(uuidCharArray, uuidLength);
84 return std::string{uuidCharArray};
90 return checksum.final();
93int main(
int argc,
char* argv[]) {
94 express::WebApp::init(argc, argv);
95 const express::legacy::in::WebApp app(
"OAuth2AuthorizationServer");
103 .
socket =
"/run/mysqld/mysqld.sock",
108 app.use(express::
middleware::JsonMiddleware());
110 const express::Router router{};
114 const std::string queryClientId{req->query(
"client_id")};
115 if (queryClientId.length() > 0) {
117 "select count(*) from client where uuid = '" + queryClientId +
"'",
118 [req, res, next, queryClientId](
const MYSQL_ROW row) {
119 if (row !=
nullptr) {
120 if (std::stoi(row[0]) > 0) {
121 VLOG(1) <<
"Valid client id '" << queryClientId <<
"'";
122 VLOG(1) <<
"Next with " << req->httpVersion <<
" " << req->method <<
" " << req->url;
125 VLOG(1) <<
"Invalid client id '" << queryClientId <<
"'";
126 res->sendStatus(401);
130 [res](
const std::string& errorString,
unsigned int errorNumber) {
131 VLOG(1) <<
"Database error: " << errorString <<
" : " << errorNumber;
132 res->sendStatus(500);
135 res->status(401).send(
"Invalid client_id");
139 router.get(
"/authorize", [&db]
APPLICATION(req, res) {
143 const std::string paramResponseType{req->query(
"response_type")};
144 const std::string paramClientId{req->query(
"client_id")};
145 const std::string paramRedirectUri{req->query(
"redirect_uri")};
146 const std::string paramScope{req->query(
"scope")};
147 const std::string paramState{req->query(
"state")};
149 VLOG(1) <<
"Query params: "
150 <<
"response_type=" << req->query(
"response_type") <<
", "
151 <<
"redirect_uri=" << req->query(
"redirect_uri") <<
", "
152 <<
"scope=" << req->query(
"scope") <<
", "
153 <<
"state=" << req->query(
"state") <<
"\n";
155 if (paramResponseType !=
"code") {
156 VLOG(1) <<
"Auth invalid, sending Bad Request";
157 res->sendStatus(400);
161 if (!paramRedirectUri.empty()) {
163 "update client set redirect_uri = '" + paramRedirectUri +
"' where uuid = '" + paramClientId +
"'",
164 [paramRedirectUri]() {
165 VLOG(1) <<
"Database: Set redirect_uri to " << paramRedirectUri;
167 [](
const std::string& errorString,
unsigned int errorNumber) {
168 VLOG(1) <<
"Database error: " << errorString <<
" : " << errorNumber;
172 if (!paramScope.empty()) {
174 "update client set scope = '" + paramScope +
"' where uuid = '" + paramClientId +
"'",
176 VLOG(1) <<
"Database: Set scope to " << paramScope;
178 [](
const std::string& errorString,
unsigned int errorNumber) {
179 VLOG(1) <<
"Database error: " << errorString <<
" : " << errorNumber;
183 if (!paramState.empty()) {
185 "update client set state = '" + paramState +
"' where uuid = '" + paramClientId +
"'",
187 VLOG(1) <<
"Database: Set state to " << paramState;
189 [](
const std::string& errorString,
unsigned int errorNumber) {
190 VLOG(1) <<
"Database error: " << errorString <<
" : " << errorNumber;
194 VLOG(1) <<
"Auth request valid, redirecting to login";
195 std::string loginUri{
"/oauth2/login"};
196 addQueryParamToUri(loginUri,
"client_id", paramClientId);
197 res->redirect(loginUri);
201 res->sendFile(
"/home/rathalin/projects/snode.c/src/oauth2/authorization_server/vue-frontend-oauth2-auth-server/dist/index.html",
204 PLOG(ERROR) << req->url;
209 router.post(
"/login", [&db]
APPLICATION(req, res) {
210 req->getAttribute<nlohmann::json>(
211 [req, res, &db](nlohmann::json& body) {
213 "select email, password_hash, password_salt, redirect_uri, state "
216 req->query(
"client_id") +
"'",
217 [req, res, &db, &body](
const MYSQL_ROW row) {
218 if (row !=
nullptr) {
219 const std::string dbEmail{row[0]};
220 const std::string dbPasswordHash{row[1]};
221 const std::string dbPasswordSalt{row[2]};
222 const std::string dbRedirectUri{row[3]};
223 const std::string dbState{row[4]};
224 const std::string queryEmail{body[
"email"]};
225 const std::string queryPassword{body[
"password"]};
227 if (dbEmail != queryEmail) {
228 res->status(401).send(
"Invalid email address");
229 }
else if (dbPasswordHash != hashSha1(dbPasswordSalt + queryPassword)) {
230 res->status(401).send(
"Invalid password");
233 const unsigned int expireMinutes{10};
234 const std::string authCode{getNewUUID()};
236 "insert into token(uuid, expire_datetime) "
239 timeToString(std::chrono::system_clock::now() + std::chrono::minutes(expireMinutes)) +
"')",
242 [res](
const std::string& errorString,
unsigned int errorNumber) {
243 VLOG(1) <<
"Database error: " << errorString <<
" : " << errorNumber;
244 res->sendStatus(500);
247 "select last_insert_id()",
248 [req, res, &db, dbState, dbRedirectUri, authCode](
const MYSQL_ROW row) {
249 if (row !=
nullptr) {
252 "set auth_code_id = '" +
253 std::string{row[0]} +
256 req->query(
"client_id") +
"'",
257 [res, dbState, dbRedirectUri, authCode]() {
259 std::string clientRedirectUri{dbRedirectUri};
260 addQueryParamToUri(clientRedirectUri,
"code", authCode);
261 if (!dbState.empty()) {
262 addQueryParamToUri(clientRedirectUri,
"state", dbState);
265 res->set(
"Access-Control-Allow-Origin",
"*");
266 const nlohmann::json responseJson = {{
"redirect_uri", clientRedirectUri}};
267 const std::string responseJsonString{responseJson.dump(4)};
268 VLOG(1) <<
"Sending json reponse: " << responseJsonString;
269 res->send(responseJsonString);
271 [res](
const std::string& errorString,
unsigned int errorNumber) {
272 VLOG(1) <<
"Database error: " << errorString <<
" : " << errorNumber;
273 res->sendStatus(500);
277 [res](
const std::string& errorString,
unsigned int errorNumber) {
278 VLOG(1) <<
"Database error: " << errorString <<
" : " << errorNumber;
279 res->sendStatus(500);
284 [res](
const std::string& errorString,
unsigned int errorNumber) {
285 VLOG(1) <<
"Database error: " << errorString <<
" : " << errorNumber;
286 res->sendStatus(500);
289 [res]([[maybe_unused]]
const std::string& key) {
290 res->sendStatus(500);
295 res->set(
"Access-Control-Allow-Origin",
"*");
296 auto queryGrantType = req->query(
"grant_type");
297 VLOG(1) <<
"GrandType: " << queryGrantType;
298 auto queryCode = req->query(
"code");
299 VLOG(1) <<
"Code: " << queryCode;
300 auto queryRedirectUri = req->query(
"redirect_uri");
301 VLOG(1) <<
"RedirectUri: " << queryRedirectUri;
302 if (queryGrantType !=
"authorization_code") {
303 res->status(400).send(
"Invalid query parameter 'grant_type', value must be 'authorization_code'");
306 if (queryCode.length() == 0) {
307 res->status(400).send(
"Missing query parameter 'code'");
310 if (queryRedirectUri.length() == 0) {
311 res->status(400).send(
"Missing query parameter 'redirect_uri'");
318 req->query(
"client_id") +
320 "and redirect_uri = '" +
321 queryRedirectUri +
"'",
322 [req, res, &db](
const MYSQL_ROW row) {
323 if (row !=
nullptr) {
324 if (std::stoi(row[0]) == 0) {
325 res->status(400).send(
"Query param 'redirect_uri' must be the same as in the initial request");
331 "on c.auth_code_id = a.id "
333 req->query(
"client_id") +
338 "and timestampdiff(second, current_timestamp(), a.expire_datetime) > 0",
339 [req, res, &db](
const MYSQL_ROW row) {
340 if (row !=
nullptr) {
341 if (std::stoi(row[0]) == 0) {
342 res->status(401).send(
"Invalid auth token");
346 const std::string accessToken{getNewUUID()};
347 const unsigned int accessTokenExpireSeconds{60 * 60};
348 const std::string refreshToken{getNewUUID()};
349 const unsigned int refreshTokenExpireSeconds{60 * 60 * 24};
351 "insert into token(uuid, expire_datetime) "
353 accessToken +
"', '" +
354 timeToString(std::chrono::system_clock::now() +
355 std::chrono::seconds(accessTokenExpireSeconds)) +
359 [res](
const std::string& errorString,
unsigned int errorNumber) {
360 VLOG(1) <<
"Database error: " << errorString <<
" : " << errorNumber;
361 res->sendStatus(500);
364 "select last_insert_id()",
365 [req, res, &db](
const MYSQL_ROW row) {
366 if (row !=
nullptr) {
369 "set access_token_id = '" +
370 std::string{row[0]} +
373 req->query(
"client_id") +
"'",
376 [res](
const std::string& errorString,
unsigned int errorNumber) {
377 VLOG(1) <<
"Database error: " << errorString <<
" : " << errorNumber;
378 res->sendStatus(500);
382 [res](
const std::string& errorString,
unsigned int errorNumber) {
383 VLOG(1) <<
"Database error: " << errorString <<
" : " << errorNumber;
384 res->sendStatus(500);
387 "insert into token(uuid, expire_datetime) "
389 refreshToken +
"', '" +
390 timeToString(std::chrono::system_clock::now() +
391 std::chrono::seconds(refreshTokenExpireSeconds)) +
395 [res](
const std::string& errorString,
unsigned int errorNumber) {
396 VLOG(1) <<
"Database error: " << errorString <<
" : " << errorNumber;
397 res->sendStatus(500);
400 "select last_insert_id()",
401 [req, res, &db, accessToken, accessTokenExpireSeconds, refreshToken](
const MYSQL_ROW row) {
402 if (row !=
nullptr) {
405 "set refresh_token_id = '" +
406 std::string{row[0]} +
409 req->query(
"client_id") +
"'",
410 [res, accessToken, accessTokenExpireSeconds, refreshToken]() {
412 const nlohmann::json jsonResponse = {{
"access_token", accessToken},
413 {
"expires_in", accessTokenExpireSeconds},
414 {
"refresh_token", refreshToken}};
415 const std::string jsonResponseString{jsonResponse.dump(4)};
416 res->send(jsonResponseString);
418 [res](
const std::string& errorString,
unsigned int errorNumber) {
419 VLOG(1) <<
"Database error: " << errorString <<
" : " << errorNumber;
420 res->sendStatus(500);
424 [res](
const std::string& errorString,
unsigned int errorNumber) {
425 VLOG(1) <<
"Database error: " << errorString <<
" : " << errorNumber;
426 res->sendStatus(500);
430 [res](
const std::string& errorString,
unsigned int errorNumber) {
431 VLOG(1) <<
"Database error: " << errorString <<
" : " << errorNumber;
432 res->sendStatus(500);
437 [res](
const std::string& errorString,
unsigned int errorNumber) {
438 VLOG(1) <<
"Database error: " << errorString <<
" : " << errorNumber;
439 res->sendStatus(500);
443 router.post(
"/token/refresh", [&db]
APPLICATION(req, res) {
444 res->set(
"Access-Control-Allow-Origin",
"*");
445 auto queryClientId = req->query(
"client_id");
446 VLOG(1) <<
"ClientId: " << queryClientId;
447 auto queryGrantType = req->query(
"grant_type");
448 VLOG(1) <<
"GrandType: " << queryGrantType;
449 auto queryRefreshToken = req->query(
"refresh_token");
450 VLOG(1) <<
"RefreshToken: " << queryRefreshToken;
451 auto queryState = req->query(
"state");
452 VLOG(1) <<
"State: " << queryState;
453 if (queryGrantType.length() == 0) {
454 res->status(400).send(
"Missing query parameter 'grant_type'");
457 if (queryGrantType !=
"refresh_token") {
458 res->status(400).send(
"Invalid query parameter 'grant_type', value must be 'refresh_token'");
461 if (queryRefreshToken.empty()) {
462 res->status(400).send(
"Missing query parameter 'refresh_token'");
468 "on c.refresh_token_id = r.id "
470 req->query(
"client_id") +
473 req->query(
"refresh_token") +
475 "and timestampdiff(second, current_timestamp(), r.expire_datetime) > 0",
476 [req, res, &db](
const MYSQL_ROW row) {
477 if (row !=
nullptr) {
478 if (std::stoi(row[0]) == 0) {
479 res->status(401).send(
"Invalid refresh token");
483 std::string accessToken{getNewUUID()};
484 unsigned int accessTokenExpireSeconds{60 * 60};
486 "insert into token(uuid, expire_datetime) "
488 accessToken +
"', '" +
489 timeToString(std::chrono::system_clock::now() + std::chrono::seconds(accessTokenExpireSeconds)) +
"')",
492 [res](
const std::string& errorString,
unsigned int errorNumber) {
493 VLOG(1) <<
"Database error: " << errorString <<
" : " << errorNumber;
494 res->sendStatus(500);
497 "select last_insert_id()",
498 [req, res, &db, accessToken, accessTokenExpireSeconds](
const MYSQL_ROW row) {
499 if (row !=
nullptr) {
502 "set access_token_id = '" +
503 std::string{row[0]} +
506 req->query(
"client_id") +
"'",
507 [res, accessToken, accessTokenExpireSeconds]() {
508 const nlohmann::json responseJson = {{
"access_token", accessToken},
509 {
"expires_in", accessTokenExpireSeconds}};
510 res->send(responseJson.dump(4));
512 [res](
const std::string& errorString,
unsigned int errorNumber) {
513 VLOG(1) <<
"Database error: " << errorString <<
" : " << errorNumber;
514 res->sendStatus(500);
518 [res](
const std::string& errorString,
unsigned int errorNumber) {
519 VLOG(1) <<
"Database error: " << errorString <<
" : " << errorNumber;
520 res->sendStatus(500);
524 [res](
const std::string& errorString,
unsigned int errorNumber) {
525 VLOG(1) <<
"Database error: " << errorString <<
" : " << errorNumber;
526 res->sendStatus(500);
530 router.post(
"/token/validate", [&db]
APPLICATION(req, res) {
531 VLOG(1) <<
"POST /token/validate";
532 req->getAttribute<nlohmann::json>([res, &db](nlohmann::json& jsonBody) {
533 if (!jsonBody.contains(
"access_token")) {
534 VLOG(1) <<
"Missing 'access_token' in json";
535 res->status(500).send(
"Missing 'access_token' in json");
538 const std::string jsonAccessToken{jsonBody[
"access_token"]};
539 if (!jsonBody.contains(
"client_id")) {
540 VLOG(1) <<
"Missing 'client_id' in json";
541 res->status(500).send(
"Missing 'client_id' in json");
544 const std::string jsonClientId{jsonBody[
"client_id"]};
549 "on c.access_token_id = a.id "
554 jsonAccessToken +
"'",
555 [res, jsonClientId, jsonAccessToken](
const MYSQL_ROW row) {
556 if (row !=
nullptr) {
557 if (std::stoi(row[0]) == 0) {
558 const nlohmann::json errorJson = {{
"error",
"Invalid access token"}};
559 VLOG(1) <<
"Sending 401: Invalid access token '" << jsonAccessToken <<
"'";
560 res->status(401).send(errorJson.dump(4));
562 VLOG(1) <<
"Sending 200: Valid access token '" << jsonAccessToken <<
"";
563 const nlohmann::json successJson = {{
"success",
"Valid access token"}};
564 res->status(200).send(successJson.dump(4));
568 [res](
const std::string& errorString,
unsigned int errorNumber) {
569 VLOG(1) <<
"Database error: " << errorString <<
" : " << errorNumber;
570 res->sendStatus(500);
575 app.use(
"/oauth2", router);
576 app.use(express::middleware::StaticMiddleware(
577 "/home/rathalin/projects/snode.c/src/oauth2/authorization_server/vue-frontend-oauth2-auth-server/dist/"));
579 app.listen(8082, [](
const express::legacy::in::WebApp::SocketAddress& socketAddress,
const core::socket::State& state) {
581 case core::socket::State::OK:
582 VLOG(1) <<
"OAuth2AuthorizationServer: listening on '" << socketAddress.toString() <<
"'";
584 case core::socket::State::DISABLED:
585 VLOG(1) <<
"OAuth2AuthorizationServer: disabled";
587 case core::socket::State::ERROR:
588 VLOG(1) <<
"OAuth2AuthorizationServer: error occurred";
590 case core::socket::State::FATAL:
591 VLOG(1) <<
"OAuth2AuthorizationServer: fatal error occurred";
596 return express::WebApp::start();
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)
MariaDBCommandSequence & exec(const std::string &sql, const std::function< void(void)> &onExec, const std::function< void(const std::string &, unsigned int)> &onError)
MariaDBClient(const MariaDBConnectionDetails &details)
int main(int argc, char *argv[])