userver-framework / userver

Production-ready C++ Asynchronous Framework with rich functionality
https://userver.tech
Apache License 2.0
2.36k stars 272 forks source link

Force update cache for auth via postgresql #558

Open h1laryz opened 4 months ago

h1laryz commented 4 months ago

Hi, I watched into documentation at userver.tech. There was an example Custom Authorization/Authentication via PostgreSQL "Creation of tokens and user registration is out of scope of this tutorial."

I made a custom basic registration:

hpp

namespace rl::handlers
{
class Register final : public userver::server::handlers::HttpHandlerBase
{
public:
    Register( const userver::components::ComponentConfig& config,
              const userver::components::ComponentContext& context );

public:
    static constexpr std::string_view kName = "handler-register";
    using HttpHandlerBase::HttpHandlerBase;

    std::string
    HandleRequestThrow( const userver::server::http::HttpRequest& request,
                        userver::server::request::RequestContext& request_context ) const override;

private:
    userver::storages::postgres::ClusterPtr pg_cluster_;
};
} // namespace rl::handlers

cpp

namespace rl::handlers
{
Register::Register( const userver::components::ComponentConfig& config,
                    const userver::components::ComponentContext& context )
    : userver::server::handlers::HttpHandlerBase{ config, context }
    , pg_cluster_{
        context.FindComponent< userver::components::Postgres >( "auth-database" ).GetCluster()
    }
{
}

std::string
Register::HandleRequestThrow( const userver::server::http::HttpRequest& request,
                              userver::server::request::RequestContext& ) const
{
    const auto request_body{ userver::formats::json::FromString( request.RequestBody() ) };

    const auto& username { request_body["username"].As<std::string>() };
    const auto& password { request_body[ "password" ].As< std::string >() };
    const auto& mail { request_body["mail"].As<std::string>() };

    const auto token = jwt::create()
                     .set_type( "JWT" )
                     .set_id( "rsa-create-example" )
                     .set_issued_now()
                     .set_expires_in( std::chrono::seconds{ 36000 } )
                     .set_payload_claim( "sample", jwt::claim( std::string{ "test" } ) )
                     .sign( jwt::algorithm::hs256( "secret" ) );

    auto trx { pg_cluster_->Begin("sample_transaction_insert_key_value", userver::storages::postgres::ClusterHostType::kMaster, {})};

    const auto pg_query_register = "INSERT INTO auth_schema.users(username, mail, password) "
        "VALUES ($1, $2, $3) "
        "RETURNING id;";

    auto trx_result{ pg_cluster_->Execute(
        userver::storages::postgres::ClusterHostType::kSlave,
        pg_query_register,
        username,
        mail,
        password ) };

    if ( trx_result.IsEmpty() )
    {
        request.SetResponseStatus( userver::server::http::HttpStatus::kBadRequest );
        return {};
    }

    const auto user_id { trx_result.AsSingleRow<int>() };

    const auto pg_query_save_token{ "INSERT INTO auth_schema.tokens(token, user_id) "
    "VALUES ($1, $2);" };

    trx_result = pg_cluster_->Execute(
    userver::storages::postgres::ClusterHostType::kMaster,
    pg_query_save_token,
        token,
        user_id);

    trx.Commit();

    userver::formats::json::ValueBuilder response;
    response[ "token" ] = token;

    return userver::formats::json::ToString( response.ExtractValue() );
}
} // namespace rl::handlers

Cache for auth from example:

namespace rl::pg::auth
{
struct UserDbInfo
{
    userver::server::auth::UserAuthInfo::Ticket token;
    std::int64_t user_id;
    std::vector< std::string > scopes;
};

struct AuthCachePolicy
{
    static constexpr std::string_view kName = "auth-pg-cache";

    using ValueType                  = UserDbInfo;
    static constexpr auto kKeyMember = &UserDbInfo::token;
    static constexpr const char* kQuery =
        "SELECT token, user_id, scopes, name FROM auth_schema.tokens";
    static constexpr const char* kUpdatedField = "updated";
    using UpdatedFieldType                     = userver::storages::postgres::TimePointTz;

    // Using crypto::algorithm::StringsEqualConstTimeComparator to avoid timing
    // attack at find(token).
    using CacheContainer =
        std::unordered_map< userver::server::auth::UserAuthInfo::Ticket,
                            UserDbInfo,
                            std::hash< userver::server::auth::UserAuthInfo::Ticket >,
                            userver::crypto::algorithm::StringsEqualConstTimeComparator >;
};

using AuthCache = userver::components::PostgreCache< AuthCachePolicy >;
} // namespace rl::pg::auth

Is it possible to force updating cache? Or it is ok to wait until it updates itself each 10 seconds?