52int main(
int argc,
char* argv[]) {
54 const express::legacy::in::WebApp app(
"OAuth2AuthorizationServer");
62 .
socket =
"/run/mysqld/mysqld.sock",
67 app.use(express::middleware::JsonMiddleware());
73 const std::string queryClientId{req->query(
"client_id")};
74 if (queryClientId.length() > 0) {
76 "select count(*) from client where uuid = '" + queryClientId +
"'",
77 [req, res, next, queryClientId](
const MYSQL_ROW row) {
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;
84 VLOG(1) <<
"Invalid client id '" << queryClientId <<
"'";
89 [res](
const std::string& errorString,
unsigned int errorNumber) {
90 VLOG(1) <<
"Database error: " << errorString <<
" : " << errorNumber;
94 res->status(401).send(
"Invalid client_id");
98 router.get(
"/authorize", [&db]
APPLICATION(req, res) {
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")};
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";
114 if (paramResponseType !=
"code") {
115 VLOG(1) <<
"Auth invalid, sending Bad Request";
116 res->sendStatus(400);
120 if (!paramRedirectUri.empty()) {
122 "update client set redirect_uri = '" + paramRedirectUri +
"' where uuid = '" + paramClientId +
"'",
123 [paramRedirectUri]() {
124 VLOG(1) <<
"Database: Set redirect_uri to " << paramRedirectUri;
126 [](
const std::string& errorString,
unsigned int errorNumber) {
127 VLOG(1) <<
"Database error: " << errorString <<
" : " << errorNumber;
131 if (!paramScope.empty()) {
133 "update client set scope = '" + paramScope +
"' where uuid = '" + paramClientId +
"'",
135 VLOG(1) <<
"Database: Set scope to " << paramScope;
137 [](
const std::string& errorString,
unsigned int errorNumber) {
138 VLOG(1) <<
"Database error: " << errorString <<
" : " << errorNumber;
142 if (!paramState.empty()) {
144 "update client set state = '" + paramState +
"' where uuid = '" + paramClientId +
"'",
146 VLOG(1) <<
"Database: Set state to " << paramState;
148 [](
const std::string& errorString,
unsigned int errorNumber) {
149 VLOG(1) <<
"Database error: " << errorString <<
" : " << errorNumber;
153 VLOG(1) <<
"Auth request valid, redirecting to login";
154 std::string loginUri{
"/oauth2/login"};
155 addQueryParamToUri(loginUri,
"client_id", paramClientId);
156 res->redirect(loginUri);
160 res->sendFile(
"/home/rathalin/projects/snode.c/src/oauth2/authorization_server/vue-frontend-oauth2-auth-server/dist/index.html",
163 PLOG(ERROR) << req->url;
168 router.post(
"/login", [&db]
APPLICATION(req, res) {
169 req->getAttribute<nlohmann::json>(
170 [req, res, &db](nlohmann::json& body) {
172 "select email, password_hash, password_salt, redirect_uri, state "
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"]};
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");
192 const unsigned int expireMinutes{10};
193 const std::string authCode{getNewUUID()};
195 "insert into token(uuid, expire_datetime) "
198 timeToString(std::chrono::system_clock::now() + std::chrono::minutes(expireMinutes)) +
"')",
201 [res](
const std::string& errorString,
unsigned int errorNumber) {
202 VLOG(1) <<
"Database error: " << errorString <<
" : " << errorNumber;
203 res->sendStatus(500);
206 "select last_insert_id()",
207 [req, res, &db, dbState, dbRedirectUri, authCode](
const MYSQL_ROW row) {
208 if (row !=
nullptr) {
211 "set auth_code_id = '" +
212 std::string{row[0]} +
215 req->query(
"client_id") +
"'",
216 [res, dbState, dbRedirectUri, authCode]() {
218 std::string clientRedirectUri{dbRedirectUri};
219 addQueryParamToUri(clientRedirectUri,
"code", authCode);
220 if (!dbState.empty()) {
221 addQueryParamToUri(clientRedirectUri,
"state", dbState);
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);
230 [res](
const std::string& errorString,
unsigned int errorNumber) {
231 VLOG(1) <<
"Database error: " << errorString <<
" : " << errorNumber;
232 res->sendStatus(500);
236 [res](
const std::string& errorString,
unsigned int errorNumber) {
237 VLOG(1) <<
"Database error: " << errorString <<
" : " << errorNumber;
238 res->sendStatus(500);
243 [res](
const std::string& errorString,
unsigned int errorNumber) {
244 VLOG(1) <<
"Database error: " << errorString <<
" : " << errorNumber;
245 res->sendStatus(500);
248 [res]([[maybe_unused]]
const std::string& key) {
249 res->sendStatus(500);
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'");
265 if (queryCode.length() == 0) {
266 res->status(400).send(
"Missing query parameter 'code'");
269 if (queryRedirectUri.length() == 0) {
270 res->status(400).send(
"Missing query parameter 'redirect_uri'");
277 req->query(
"client_id") +
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");
290 "on c.auth_code_id = a.id "
292 req->query(
"client_id") +
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");
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};
310 "insert into token(uuid, expire_datetime) "
312 accessToken +
"', '" +
313 timeToString(std::chrono::system_clock::now() +
314 std::chrono::seconds(accessTokenExpireSeconds)) +
318 [res](
const std::string& errorString,
unsigned int errorNumber) {
319 VLOG(1) <<
"Database error: " << errorString <<
" : " << errorNumber;
320 res->sendStatus(500);
323 "select last_insert_id()",
324 [req, res, &db](
const MYSQL_ROW row) {
325 if (row !=
nullptr) {
328 "set access_token_id = '" +
329 std::string{row[0]} +
332 req->query(
"client_id") +
"'",
335 [res](
const std::string& errorString,
unsigned int errorNumber) {
336 VLOG(1) <<
"Database error: " << errorString <<
" : " << errorNumber;
337 res->sendStatus(500);
341 [res](
const std::string& errorString,
unsigned int errorNumber) {
342 VLOG(1) <<
"Database error: " << errorString <<
" : " << errorNumber;
343 res->sendStatus(500);
346 "insert into token(uuid, expire_datetime) "
348 refreshToken +
"', '" +
349 timeToString(std::chrono::system_clock::now() +
350 std::chrono::seconds(refreshTokenExpireSeconds)) +
354 [res](
const std::string& errorString,
unsigned int errorNumber) {
355 VLOG(1) <<
"Database error: " << errorString <<
" : " << errorNumber;
356 res->sendStatus(500);
359 "select last_insert_id()",
360 [req, res, &db, accessToken, accessTokenExpireSeconds, refreshToken](
const MYSQL_ROW row) {
361 if (row !=
nullptr) {
364 "set refresh_token_id = '" +
365 std::string{row[0]} +
368 req->query(
"client_id") +
"'",
369 [res, accessToken, accessTokenExpireSeconds, refreshToken]() {
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);
377 [res](
const std::string& errorString,
unsigned int errorNumber) {
378 VLOG(1) <<
"Database error: " << errorString <<
" : " << errorNumber;
379 res->sendStatus(500);
383 [res](
const std::string& errorString,
unsigned int errorNumber) {
384 VLOG(1) <<
"Database error: " << errorString <<
" : " << errorNumber;
385 res->sendStatus(500);
389 [res](
const std::string& errorString,
unsigned int errorNumber) {
390 VLOG(1) <<
"Database error: " << errorString <<
" : " << errorNumber;
391 res->sendStatus(500);
396 [res](
const std::string& errorString,
unsigned int errorNumber) {
397 VLOG(1) <<
"Database error: " << errorString <<
" : " << errorNumber;
398 res->sendStatus(500);
402 router.post(
"/token/refresh", [&db]
APPLICATION(req, res) {
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'");
416 if (queryGrantType !=
"refresh_token") {
417 res->status(400).send(
"Invalid query parameter 'grant_type', value must be 'refresh_token'");
420 if (queryRefreshToken.empty()) {
421 res->status(400).send(
"Missing query parameter 'refresh_token'");
427 "on c.refresh_token_id = r.id "
429 req->query(
"client_id") +
432 req->query(
"refresh_token") +
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");
442 std::string accessToken{getNewUUID()};
443 unsigned int accessTokenExpireSeconds{60 * 60};
445 "insert into token(uuid, expire_datetime) "
447 accessToken +
"', '" +
448 timeToString(std::chrono::system_clock::now() + std::chrono::seconds(accessTokenExpireSeconds)) +
"')",
451 [res](
const std::string& errorString,
unsigned int errorNumber) {
452 VLOG(1) <<
"Database error: " << errorString <<
" : " << errorNumber;
453 res->sendStatus(500);
456 "select last_insert_id()",
457 [req, res, &db, accessToken, accessTokenExpireSeconds](
const MYSQL_ROW row) {
458 if (row !=
nullptr) {
461 "set access_token_id = '" +
462 std::string{row[0]} +
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));
471 [res](
const std::string& errorString,
unsigned int errorNumber) {
472 VLOG(1) <<
"Database error: " << errorString <<
" : " << errorNumber;
473 res->sendStatus(500);
477 [res](
const std::string& errorString,
unsigned int errorNumber) {
478 VLOG(1) <<
"Database error: " << errorString <<
" : " << errorNumber;
479 res->sendStatus(500);
483 [res](
const std::string& errorString,
unsigned int errorNumber) {
484 VLOG(1) <<
"Database error: " << errorString <<
" : " << errorNumber;
485 res->sendStatus(500);
489 router.post(
"/token/validate", [&db]
APPLICATION(req, res) {
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");
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");
503 const std::string jsonClientId{jsonBody[
"client_id"]};
508 "on c.access_token_id = a.id "
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));
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));
527 [res](
const std::string& errorString,
unsigned int errorNumber) {
528 VLOG(1) <<
"Database error: " << errorString <<
" : " << errorNumber;
529 res->sendStatus(500);
534 app.use(
"/oauth2", router);
535 app.use(express::middleware::StaticMiddleware(
536 "/home/rathalin/projects/snode.c/src/oauth2/authorization_server/vue-frontend-oauth2-auth-server/dist/"));
538 app.listen(8082, [](
const express::legacy::in::WebApp::SocketAddress& socketAddress,
const core::socket::State& state) {
540 case core::socket::State::OK:
541 VLOG(1) <<
"OAuth2AuthorizationServer: listening on '" << socketAddress.toString() <<
"'";
543 case core::socket::State::DISABLED:
544 VLOG(1) <<
"OAuth2AuthorizationServer: disabled";
546 case core::socket::State::ERROR:
547 VLOG(1) <<
"OAuth2AuthorizationServer: error occurred";
549 case core::socket::State::FATAL:
550 VLOG(1) <<
"OAuth2AuthorizationServer: fatal error occurred";