93int main(
int argc,
char* argv[]) {
94 express::WebApp::init(argc, argv);
95 const express::legacy::in::WebApp app(
"OAuth2AuthorizationServer");
104 .
socket =
"/run/mysqld/mysqld.sock",
111 VLOG(0) <<
"MySQL connected";
113 VLOG(0) <<
"MySQL disconnected";
117 app.use(express::
middleware::JsonMiddleware());
119 const express::Router router{};
123 const std::string queryClientId{req->query(
"client_id")};
124 if (queryClientId.length() > 0) {
126 "select count(*) from client where uuid = '" + queryClientId +
"'",
127 [req, res, next, queryClientId](
const MYSQL_ROW row) {
128 if (row !=
nullptr) {
129 if (std::stoi(row[0]) > 0) {
130 VLOG(1) <<
"Valid client id '" << queryClientId <<
"'";
131 VLOG(1) <<
"Next with " << req->httpVersion <<
" " << req->method <<
" " << req->url;
134 VLOG(1) <<
"Invalid client id '" << queryClientId <<
"'";
135 res->sendStatus(401);
139 [res](
const std::string& errorString,
unsigned int errorNumber) {
140 VLOG(1) <<
"Database error: " << errorString <<
" : " << errorNumber;
141 res->sendStatus(500);
144 res->status(401).send(
"Invalid client_id");
148 router.get(
"/authorize", [&db]
APPLICATION(req, res) {
152 const std::string paramResponseType{req->query(
"response_type")};
153 const std::string paramClientId{req->query(
"client_id")};
154 const std::string paramRedirectUri{req->query(
"redirect_uri")};
155 const std::string paramScope{req->query(
"scope")};
156 const std::string paramState{req->query(
"state")};
158 VLOG(1) <<
"Query params: "
159 <<
"response_type=" << req->query(
"response_type") <<
", "
160 <<
"redirect_uri=" << req->query(
"redirect_uri") <<
", "
161 <<
"scope=" << req->query(
"scope") <<
", "
162 <<
"state=" << req->query(
"state") <<
"\n";
164 if (paramResponseType !=
"code") {
165 VLOG(1) <<
"Auth invalid, sending Bad Request";
166 res->sendStatus(400);
170 if (!paramRedirectUri.empty()) {
172 "update client set redirect_uri = '" + paramRedirectUri +
"' where uuid = '" + paramClientId +
"'",
173 [paramRedirectUri]() {
174 VLOG(1) <<
"Database: Set redirect_uri to " << paramRedirectUri;
176 [](
const std::string& errorString,
unsigned int errorNumber) {
177 VLOG(1) <<
"Database error: " << errorString <<
" : " << errorNumber;
181 if (!paramScope.empty()) {
183 "update client set scope = '" + paramScope +
"' where uuid = '" + paramClientId +
"'",
185 VLOG(1) <<
"Database: Set scope to " << paramScope;
187 [](
const std::string& errorString,
unsigned int errorNumber) {
188 VLOG(1) <<
"Database error: " << errorString <<
" : " << errorNumber;
192 if (!paramState.empty()) {
194 "update client set state = '" + paramState +
"' where uuid = '" + paramClientId +
"'",
196 VLOG(1) <<
"Database: Set state to " << paramState;
198 [](
const std::string& errorString,
unsigned int errorNumber) {
199 VLOG(1) <<
"Database error: " << errorString <<
" : " << errorNumber;
203 VLOG(1) <<
"Auth request valid, redirecting to login";
204 std::string loginUri{
"/oauth2/login"};
205 addQueryParamToUri(loginUri,
"client_id", paramClientId);
206 res->redirect(loginUri);
210 res->sendFile(
"/home/rathalin/projects/snode.c/src/oauth2/authorization_server/vue-frontend-oauth2-auth-server/dist/index.html",
213 PLOG(ERROR) << req->url;
218 router.post(
"/login", [&db]
APPLICATION(req, res) {
219 req->getAttribute<nlohmann::json>(
220 [req, res, &db](nlohmann::json& body) {
222 "select email, password_hash, password_salt, redirect_uri, state "
225 req->query(
"client_id") +
"'",
226 [req, res, &db, &body](
const MYSQL_ROW row) {
227 if (row !=
nullptr) {
228 const std::string dbEmail{row[0]};
229 const std::string dbPasswordHash{row[1]};
230 const std::string dbPasswordSalt{row[2]};
231 const std::string dbRedirectUri{row[3]};
232 const std::string dbState{row[4]};
233 const std::string queryEmail{body[
"email"]};
234 const std::string queryPassword{body[
"password"]};
236 if (dbEmail != queryEmail) {
237 res->status(401).send(
"Invalid email address");
238 }
else if (dbPasswordHash != hashSha1(dbPasswordSalt + queryPassword)) {
239 res->status(401).send(
"Invalid password");
242 const unsigned int expireMinutes{10};
243 const std::string authCode{getNewUUID()};
245 "insert into token(uuid, expire_datetime) "
248 timeToString(std::chrono::system_clock::now() + std::chrono::minutes(expireMinutes)) +
"')",
251 [res](
const std::string& errorString,
unsigned int errorNumber) {
252 VLOG(1) <<
"Database error: " << errorString <<
" : " << errorNumber;
253 res->sendStatus(500);
256 "select last_insert_id()",
257 [req, res, &db, dbState, dbRedirectUri, authCode](
const MYSQL_ROW row) {
258 if (row !=
nullptr) {
261 "set auth_code_id = '" +
262 std::string{row[0]} +
265 req->query(
"client_id") +
"'",
266 [res, dbState, dbRedirectUri, authCode]() {
268 std::string clientRedirectUri{dbRedirectUri};
269 addQueryParamToUri(clientRedirectUri,
"code", authCode);
270 if (!dbState.empty()) {
271 addQueryParamToUri(clientRedirectUri,
"state", dbState);
274 res->set(
"Access-Control-Allow-Origin",
"*");
275 const nlohmann::json responseJson = {{
"redirect_uri", clientRedirectUri}};
276 const std::string responseJsonString{responseJson.dump(4)};
277 VLOG(1) <<
"Sending json reponse: " << responseJsonString;
278 res->send(responseJsonString);
280 [res](
const std::string& errorString,
unsigned int errorNumber) {
281 VLOG(1) <<
"Database error: " << errorString <<
" : " << errorNumber;
282 res->sendStatus(500);
286 [res](
const std::string& errorString,
unsigned int errorNumber) {
287 VLOG(1) <<
"Database error: " << errorString <<
" : " << errorNumber;
288 res->sendStatus(500);
293 [res](
const std::string& errorString,
unsigned int errorNumber) {
294 VLOG(1) <<
"Database error: " << errorString <<
" : " << errorNumber;
295 res->sendStatus(500);
298 [res]([[maybe_unused]]
const std::string& key) {
299 res->sendStatus(500);
304 res->set(
"Access-Control-Allow-Origin",
"*");
305 auto queryGrantType = req->query(
"grant_type");
306 VLOG(1) <<
"GrandType: " << queryGrantType;
307 auto queryCode = req->query(
"code");
308 VLOG(1) <<
"Code: " << queryCode;
309 auto queryRedirectUri = req->query(
"redirect_uri");
310 VLOG(1) <<
"RedirectUri: " << queryRedirectUri;
311 if (queryGrantType !=
"authorization_code") {
312 res->status(400).send(
"Invalid query parameter 'grant_type', value must be 'authorization_code'");
315 if (queryCode.length() == 0) {
316 res->status(400).send(
"Missing query parameter 'code'");
319 if (queryRedirectUri.length() == 0) {
320 res->status(400).send(
"Missing query parameter 'redirect_uri'");
327 req->query(
"client_id") +
329 "and redirect_uri = '" +
330 queryRedirectUri +
"'",
331 [req, res, &db](
const MYSQL_ROW row) {
332 if (row !=
nullptr) {
333 if (std::stoi(row[0]) == 0) {
334 res->status(400).send(
"Query param 'redirect_uri' must be the same as in the initial request");
340 "on c.auth_code_id = a.id "
342 req->query(
"client_id") +
347 "and timestampdiff(second, current_timestamp(), a.expire_datetime) > 0",
348 [req, res, &db](
const MYSQL_ROW row) {
349 if (row !=
nullptr) {
350 if (std::stoi(row[0]) == 0) {
351 res->status(401).send(
"Invalid auth token");
355 const std::string accessToken{getNewUUID()};
356 const unsigned int accessTokenExpireSeconds{60 * 60};
357 const std::string refreshToken{getNewUUID()};
358 const unsigned int refreshTokenExpireSeconds{60 * 60 * 24};
360 "insert into token(uuid, expire_datetime) "
362 accessToken +
"', '" +
363 timeToString(std::chrono::system_clock::now() +
364 std::chrono::seconds(accessTokenExpireSeconds)) +
368 [res](
const std::string& errorString,
unsigned int errorNumber) {
369 VLOG(1) <<
"Database error: " << errorString <<
" : " << errorNumber;
370 res->sendStatus(500);
373 "select last_insert_id()",
374 [req, res, &db](
const MYSQL_ROW row) {
375 if (row !=
nullptr) {
378 "set access_token_id = '" +
379 std::string{row[0]} +
382 req->query(
"client_id") +
"'",
385 [res](
const std::string& errorString,
unsigned int errorNumber) {
386 VLOG(1) <<
"Database error: " << errorString <<
" : " << errorNumber;
387 res->sendStatus(500);
391 [res](
const std::string& errorString,
unsigned int errorNumber) {
392 VLOG(1) <<
"Database error: " << errorString <<
" : " << errorNumber;
393 res->sendStatus(500);
396 "insert into token(uuid, expire_datetime) "
398 refreshToken +
"', '" +
399 timeToString(std::chrono::system_clock::now() +
400 std::chrono::seconds(refreshTokenExpireSeconds)) +
404 [res](
const std::string& errorString,
unsigned int errorNumber) {
405 VLOG(1) <<
"Database error: " << errorString <<
" : " << errorNumber;
406 res->sendStatus(500);
409 "select last_insert_id()",
410 [req, res, &db, accessToken, accessTokenExpireSeconds, refreshToken](
const MYSQL_ROW row) {
411 if (row !=
nullptr) {
414 "set refresh_token_id = '" +
415 std::string{row[0]} +
418 req->query(
"client_id") +
"'",
419 [res, accessToken, accessTokenExpireSeconds, refreshToken]() {
421 const nlohmann::json jsonResponse = {{
"access_token", accessToken},
422 {
"expires_in", accessTokenExpireSeconds},
423 {
"refresh_token", refreshToken}};
424 const std::string jsonResponseString{jsonResponse.dump(4)};
425 res->send(jsonResponseString);
427 [res](
const std::string& errorString,
unsigned int errorNumber) {
428 VLOG(1) <<
"Database error: " << errorString <<
" : " << errorNumber;
429 res->sendStatus(500);
433 [res](
const std::string& errorString,
unsigned int errorNumber) {
434 VLOG(1) <<
"Database error: " << errorString <<
" : " << errorNumber;
435 res->sendStatus(500);
439 [res](
const std::string& errorString,
unsigned int errorNumber) {
440 VLOG(1) <<
"Database error: " << errorString <<
" : " << errorNumber;
441 res->sendStatus(500);
446 [res](
const std::string& errorString,
unsigned int errorNumber) {
447 VLOG(1) <<
"Database error: " << errorString <<
" : " << errorNumber;
448 res->sendStatus(500);
452 router.post(
"/token/refresh", [&db]
APPLICATION(req, res) {
453 res->set(
"Access-Control-Allow-Origin",
"*");
454 auto queryClientId = req->query(
"client_id");
455 VLOG(1) <<
"ClientId: " << queryClientId;
456 auto queryGrantType = req->query(
"grant_type");
457 VLOG(1) <<
"GrandType: " << queryGrantType;
458 auto queryRefreshToken = req->query(
"refresh_token");
459 VLOG(1) <<
"RefreshToken: " << queryRefreshToken;
460 auto queryState = req->query(
"state");
461 VLOG(1) <<
"State: " << queryState;
462 if (queryGrantType.length() == 0) {
463 res->status(400).send(
"Missing query parameter 'grant_type'");
466 if (queryGrantType !=
"refresh_token") {
467 res->status(400).send(
"Invalid query parameter 'grant_type', value must be 'refresh_token'");
470 if (queryRefreshToken.empty()) {
471 res->status(400).send(
"Missing query parameter 'refresh_token'");
477 "on c.refresh_token_id = r.id "
479 req->query(
"client_id") +
482 req->query(
"refresh_token") +
484 "and timestampdiff(second, current_timestamp(), r.expire_datetime) > 0",
485 [req, res, &db](
const MYSQL_ROW row) {
486 if (row !=
nullptr) {
487 if (std::stoi(row[0]) == 0) {
488 res->status(401).send(
"Invalid refresh token");
492 std::string accessToken{getNewUUID()};
493 unsigned int accessTokenExpireSeconds{60 * 60};
495 "insert into token(uuid, expire_datetime) "
497 accessToken +
"', '" +
498 timeToString(std::chrono::system_clock::now() + std::chrono::seconds(accessTokenExpireSeconds)) +
"')",
501 [res](
const std::string& errorString,
unsigned int errorNumber) {
502 VLOG(1) <<
"Database error: " << errorString <<
" : " << errorNumber;
503 res->sendStatus(500);
506 "select last_insert_id()",
507 [req, res, &db, accessToken, accessTokenExpireSeconds](
const MYSQL_ROW row) {
508 if (row !=
nullptr) {
511 "set access_token_id = '" +
512 std::string{row[0]} +
515 req->query(
"client_id") +
"'",
516 [res, accessToken, accessTokenExpireSeconds]() {
517 const nlohmann::json responseJson = {{
"access_token", accessToken},
518 {
"expires_in", accessTokenExpireSeconds}};
519 res->send(responseJson.dump(4));
521 [res](
const std::string& errorString,
unsigned int errorNumber) {
522 VLOG(1) <<
"Database error: " << errorString <<
" : " << errorNumber;
523 res->sendStatus(500);
527 [res](
const std::string& errorString,
unsigned int errorNumber) {
528 VLOG(1) <<
"Database error: " << errorString <<
" : " << errorNumber;
529 res->sendStatus(500);
533 [res](
const std::string& errorString,
unsigned int errorNumber) {
534 VLOG(1) <<
"Database error: " << errorString <<
" : " << errorNumber;
535 res->sendStatus(500);
539 router.post(
"/token/validate", [&db]
APPLICATION(req, res) {
540 VLOG(1) <<
"POST /token/validate";
541 req->getAttribute<nlohmann::json>([res, &db](nlohmann::json& jsonBody) {
542 if (!jsonBody.contains(
"access_token")) {
543 VLOG(1) <<
"Missing 'access_token' in json";
544 res->status(500).send(
"Missing 'access_token' in json");
547 const std::string jsonAccessToken{jsonBody[
"access_token"]};
548 if (!jsonBody.contains(
"client_id")) {
549 VLOG(1) <<
"Missing 'client_id' in json";
550 res->status(500).send(
"Missing 'client_id' in json");
553 const std::string jsonClientId{jsonBody[
"client_id"]};
558 "on c.access_token_id = a.id "
563 jsonAccessToken +
"'",
564 [res, jsonClientId, jsonAccessToken](
const MYSQL_ROW row) {
565 if (row !=
nullptr) {
566 if (std::stoi(row[0]) == 0) {
567 const nlohmann::json errorJson = {{
"error",
"Invalid access token"}};
568 VLOG(1) <<
"Sending 401: Invalid access token '" << jsonAccessToken <<
"'";
569 res->status(401).send(errorJson.dump(4));
571 VLOG(1) <<
"Sending 200: Valid access token '" << jsonAccessToken <<
"";
572 const nlohmann::json successJson = {{
"success",
"Valid access token"}};
573 res->status(200).send(successJson.dump(4));
577 [res](
const std::string& errorString,
unsigned int errorNumber) {
578 VLOG(1) <<
"Database error: " << errorString <<
" : " << errorNumber;
579 res->sendStatus(500);
584 app.use(
"/oauth2", router);
585 app.use(express::middleware::StaticMiddleware(
586 "/home/rathalin/projects/snode.c/src/oauth2/authorization_server/vue-frontend-oauth2-auth-server/dist/"));
588 app.listen(8082, [](
const express::legacy::in::WebApp::SocketAddress& socketAddress,
const core::socket::State& state) {
590 case core::socket::State::OK:
591 VLOG(1) <<
"OAuth2AuthorizationServer: listening on '" << socketAddress.toString() <<
"'";
593 case core::socket::State::DISABLED:
594 VLOG(1) <<
"OAuth2AuthorizationServer: disabled";
596 case core::socket::State::ERROR:
597 VLOG(1) <<
"OAuth2AuthorizationServer: error occurred";
599 case core::socket::State::FATAL:
600 VLOG(1) <<
"OAuth2AuthorizationServer: fatal error occurred";
605 return express::WebApp::start();