forked from anydistro/bxt
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This changes how tokens are used by introducing a two different tokens: short-living access token and long-living refresh token. BREAKING CHANGES: Endpoints now require `access_token` field passed either in cookie or as bearer. `/api/auth` returns `refresh_token` and `access_token`. New `/api/auth/refresh` and `/api/auth/revoke` endpoints are added while `/api/verify` is deleted.
- Loading branch information
1 parent
3bb387b
commit 0468955
Showing
15 changed files
with
566 additions
and
127 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
/* === This file is part of bxt === | ||
* | ||
* SPDX-FileCopyrightText: 2024 Artem Grinev <agrinev@manjaro.org> | ||
* SPDX-License-Identifier: AGPL-3.0-or-later | ||
* | ||
*/ | ||
|
||
#pragma once | ||
|
||
namespace bxt::Presentation::Names { | ||
constexpr inline auto TokenType = "token_type"; | ||
constexpr inline auto AccessToken = "access_token"; | ||
constexpr inline auto RefreshToken = "refresh_token"; | ||
constexpr inline auto Storage = "storage"; | ||
constexpr inline auto UserName = "username"; | ||
constexpr inline auto CookieStorage = "cookie"; | ||
constexpr inline auto BearerStorage = "bearer"; | ||
constexpr inline auto TokenKind = "kind"; | ||
} // namespace bxt::Presentation::Names |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
/* === This file is part of bxt === | ||
* | ||
* SPDX-FileCopyrightText: 2024 Artem Grinev <agrinev@manjaro.org> | ||
* SPDX-License-Identifier: AGPL-3.0-or-later | ||
* | ||
*/ | ||
|
||
#include "Token.h" | ||
|
||
#include <fmt/format.h> | ||
#include <jwt-cpp/traits/nlohmann-json/defaults.h> | ||
|
||
namespace bxt::Presentation { | ||
|
||
constexpr auto AccessTokenExpiration = std::chrono::minutes {15}; | ||
constexpr auto RefreshTokenExpiration = std::chrono::weeks {2}; | ||
|
||
std::expected<Token, std::string> | ||
bxt::Presentation::Token::verify_jwt(const std::string &jwt, | ||
const std::string &issuer, | ||
const std::string &secret) { | ||
try { | ||
const auto decoded = jwt::decode(jwt); | ||
const auto verifier = | ||
jwt::verify() | ||
.allow_algorithm(jwt::algorithm::hs256 {secret}) | ||
.with_issuer(issuer); | ||
|
||
verifier.verify(decoded); | ||
|
||
if (!decoded.has_payload_claim(Names::Storage)) { | ||
return std::unexpected("No token storage provided"); | ||
} | ||
|
||
if (!decoded.has_payload_claim(Names::TokenKind) | ||
|| !decoded.has_payload_claim(Names::UserName)) { | ||
return std::unexpected("No token kind or username provided"); | ||
} | ||
|
||
auto storage = decoded.get_payload_claim(Names::Storage).as_string(); | ||
auto kind = decoded.get_payload_claim(Names::TokenKind).as_string(); | ||
auto username = decoded.get_payload_claim(Names::UserName).as_string(); | ||
|
||
return Token {username, | ||
kind == Names::AccessToken ? Token::Kind::Access | ||
: Token::Kind::Refresh, | ||
storage == Names::CookieStorage ? Token::Storage::Cookie | ||
: Token::Storage::Bearer, | ||
decoded.get_issued_at(), | ||
decoded.get_expires_at(), | ||
jwt}; | ||
} catch (const std::exception &exception) { | ||
return std::unexpected(fmt::format( | ||
"Token is invalid, the error is: \"{}\"", exception.what())); | ||
} | ||
} | ||
|
||
bxt::Presentation::Token::Token(std::string name, Kind kind, Storage storage) | ||
: m_kind(kind), | ||
m_storage(storage), | ||
m_name(std::move(name)), | ||
m_issued_at(std::chrono::system_clock::now()) { | ||
using namespace std::chrono_literals; | ||
if (kind == Kind::Access) { | ||
m_expires_at = m_issued_at + AccessTokenExpiration; | ||
} else { | ||
m_expires_at = m_issued_at + RefreshTokenExpiration; | ||
} | ||
} | ||
|
||
std::string bxt::Presentation::Token::generate_jwt(const std::string &issuer, | ||
const std::string &secret) { | ||
if (m_cached_jwt.has_value()) { | ||
try { | ||
const auto decoded = jwt::decode(*m_cached_jwt); | ||
const auto verifier = | ||
jwt::verify() | ||
.allow_algorithm(jwt::algorithm::hs256 {secret}) | ||
.with_issuer(issuer); | ||
|
||
verifier.verify(decoded); | ||
return *m_cached_jwt; | ||
} catch (const std::exception &exception) {} | ||
} | ||
|
||
m_cached_jwt = | ||
jwt::create() | ||
.set_payload_claim(Names::UserName, m_name) | ||
.set_payload_claim(Names::Storage, bxt::to_string(m_storage)) | ||
.set_payload_claim(Names::TokenKind, bxt::to_string(m_kind)) | ||
.set_issuer(issuer) | ||
.set_type("JWS") | ||
.set_issued_at(m_issued_at) | ||
.set_expires_at(m_expires_at) | ||
.sign(jwt::algorithm::hs256 {secret}); | ||
|
||
return *m_cached_jwt; | ||
} | ||
} // namespace bxt::Presentation |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
/* === This file is part of bxt === | ||
* | ||
* SPDX-FileCopyrightText: 2024 Artem Grinev <agrinev@manjaro.org> | ||
* SPDX-License-Identifier: AGPL-3.0-or-later | ||
* | ||
*/ | ||
#pragma once | ||
|
||
#include "presentation/Names.h" | ||
#include "utilities/to_string.h" | ||
|
||
#include <chrono> | ||
#include <expected> | ||
#include <optional> | ||
#include <string> | ||
#include <utility> | ||
|
||
namespace bxt::Presentation { | ||
|
||
class Token { | ||
public: | ||
using time_point = std::chrono::system_clock::time_point; | ||
enum Kind { Access, Refresh }; | ||
enum Storage { Cookie, Bearer }; | ||
Token(std::string name, Kind kind, Storage storage); | ||
|
||
std::string generate_jwt(const std::string &issuer, | ||
const std::string &secret); | ||
|
||
static std::expected<Token, std::string> | ||
verify_jwt(const std::string &jwt, | ||
const std::string &issuer, | ||
const std::string &secret); | ||
|
||
Kind kind() const { return m_kind; } | ||
|
||
Storage storage() const { return m_storage; } | ||
|
||
std::string name() const { return m_name; } | ||
|
||
time_point issued_at() const { return m_issued_at; } | ||
|
||
time_point expires_at() const { return m_expires_at; } | ||
|
||
private: | ||
Token(std::string name, | ||
Kind kind, | ||
Storage storage, | ||
time_point issued_at, | ||
time_point expires_at, | ||
std::optional<std::string> cached_jwt = std::nullopt) | ||
: m_kind(kind), | ||
m_storage(storage), | ||
m_name(std::move(name)), | ||
m_issued_at(issued_at), | ||
m_expires_at(expires_at), | ||
m_cached_jwt(std::move(cached_jwt)) {} | ||
Kind m_kind = Kind::Access; | ||
Storage m_storage = Storage::Cookie; | ||
std::string m_name; | ||
time_point m_issued_at; | ||
time_point m_expires_at; | ||
std::optional<std::string> m_cached_jwt = std::nullopt; | ||
}; | ||
} // namespace bxt::Presentation | ||
|
||
template<> | ||
inline std::string bxt::to_string(const Presentation::Token::Storage &storage) { | ||
switch (storage) { | ||
case Presentation::Token::Storage::Cookie: | ||
return Presentation::Names::CookieStorage; | ||
case Presentation::Token::Storage::Bearer: | ||
return Presentation::Names::BearerStorage; | ||
default: return "Unknown"; | ||
} | ||
} | ||
|
||
template<> | ||
inline std::string bxt::to_string(const Presentation::Token::Kind &kind) { | ||
switch (kind) { | ||
case Presentation::Token::Kind::Access: | ||
return Presentation::Names::AccessToken; | ||
case Presentation::Token::Kind::Refresh: | ||
return Presentation::Names::RefreshToken; | ||
default: return "Unknown"; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.