Files
logos-protocol/cpp/token_manager.h
T
Dario LipicarandClaude Opus 5 03842db5c1 feat(windows): named pipes, an explicit lp_* ABI, and a cross target (#58)
* feat(windows): port logos_socket_paths and add a cross target

logos_socket_paths.cpp is the only POSIX-bound file in logos-protocol. All of
it is unix-domain-socket machinery, and on Windows the local transport is named
pipes (QLocalServer maps a name to \\.\pipe\<name>), where none of the
assumptions hold: a pipe has no inode to lstat/chown/chmod -- access comes from
a security descriptor set at CreateNamedPipe time -- and a pipe cannot outlive
its last handle, so a hard-killed process leaves nothing behind.

isSocketDead and reapStaleSockets are therefore not merely unimplemented on
Windows, they are vacuous: the state they detect cannot arise. Both return the
fail-closed answer (false / 0), matching the documented contract that an
endpoint is never reported dead unless certain.

applySocketPerms deliberately does NOT no-op. With no policy requested it
returns true, as on POSIX. But when LOGOS_SOCKET_GROUP or LOGOS_SOCKET_MODE
*are* set it fails with an explanatory error, because silently returning true
would leave the endpoint more permissive than the operator asked for -- the one
direction this file is careful never to go (cf. the chgrp-then-chmod ordering
in the POSIX branch). Granting a pipe to a group needs a DACL plus a
group->SID resolver; until that exists, refuse loudly.

Also gates qt6.wrapQtAppsNoGuiHook behind !isWindows and sets dontWrapQtApps.
Both halves are required: the hook does not even evaluate for a mingw host, it
would be inert anyway (wrap-qt-apps-hook.sh skips anything that is not ELF or
Mach-O), and qtbase's setup hook hard-errors in qtPreHook unless
dontWrapQtApps is set.

Header contract updated per function. POSIX branch unchanged and still compiles.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix: make the Boost.System component optional, not required

find_package(Boost REQUIRED COMPONENTS system) hard-fails on Boost 1.89:

    Could not find a package configuration file provided by "boost_system"

Boost.System has been header-only for years, and 1.89 finally dropped the
compiled boost_system library, so no boost_systemConfig.cmake is installed at
all. The COMPONENTS request was not gratuitous though -- on 1.87 the
Boost::system imported target is only exported when the component is asked
for, which is what the previous comment recorded.

So ask optionally and fall back to Boost::headers, which supplies the same
header-only error_code either way. The choice is by BOOST VERSION, not by
platform: this is not a Windows quirk, it simply surfaced first there because
the Windows target pins a newer nixpkgs (Boost 1.89) than the native one
(Boost 1.87).

Verified both ways -- native aarch64-darwin still selects Boost::system:
    -- Boost.System target: Boost::system (Boost 1.87.0)
and the build completes unchanged.

Also adds QT_HOST_PATH / QT_ADDITIONAL_HOST_PACKAGES_PREFIX_PATH for the
Windows target. Qt6RemoteObjectsDependencies.cmake declares
    set(__qt_RemoteObjects_tool_deps "Qt6RemoteObjectsTools;6.11.1")
and Qt6RemoteObjectsTools holds repc, which must RUN on the build machine --
so under cross it lives in the build-platform Qt, not the mingw one. Without
these, find_package reports the thoroughly misleading "Expected Config file at
<qtbase>/lib/cmake/Qt6RemoteObjects ... does NOT exist": the TARGET config is
found fine; it is the HOST tool package that is missing. Every Qt-consuming
repo will need this, so it should be hoisted into logos-nix rather than
repeated.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* refactor: declare the lp_* C ABI explicitly instead of relying on auto-export

Adds LP_API (__declspec(dllexport) when building the shared library, default
visibility elsewhere) to the 21 lp_* entry points, and defines
LOGOS_PROTOCOL_BUILDING_SHARED for the shared target only, so the static
archive leaves LP_API empty and its consumers need no import library.

This is NOT a bug fix, contrary to what the concern in the Windows plan
suggested. Measured on the cross-built DLL, before and after:

    before:  export table 0x2ece (11982 symbols), lp_* present: 21
    after:   export table 0x15   (   21 symbols), lp_* present: 21

GNU ld's PE auto-export was already exporting lp_* -- along with roughly
twelve thousand other symbols. The worry was that logos_module_impl.h's
__declspec(dllexport) would disable auto-export image-wide and silently drop
lp_*; it does not, because no translation unit in logos_protocol includes that
header (it is listed in PROTOCOL_SOURCES for IDE visibility only).

What this does buy is worth having anyway: the exported surface is now the ABI
we actually declare rather than whatever happens to have external linkage, it
stops being contingent on auto-export staying enabled -- which the very next
TU to gain a dllexport would silently end -- and it drops ~12k incidental
symbols from the export table.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix: relax the Boost.System requirement in the EXPORTED cmake config too

The previous commit fixed cpp/CMakeLists.txt but left
logos-protocolConfig.cmake.in still doing

    find_dependency(Boost REQUIRED COMPONENTS system)

so logos-protocol itself built fine on Boost 1.89 while every CONSUMER of its
installed CMake package failed at configure time -- caught by logos-qt-sdk,
which is the first downstream repo to be cross-built.

Worth noting as a general trap: a package can be internally consistent and
still ship a broken contract, because the exported config is a separate
artifact from the build. Anything changed in one has to be checked in the
other.

Verified both directions: the Windows cross builds of logos-cpp-sdk and
logos-qt-sdk now succeed, and a native aarch64-darwin logos-qt-sdk build --
which consumes this same config against Boost 1.87 -- still succeeds.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* feat(windows): mark the types that must exist once per process

PE has no symbol interposition. ELF and Mach-O interpose across the whole
image set, so when liblogos_core exports TokenManager::instance() every
other image binds to that one definition and the function-local
`static TokenManager instance;` is genuinely a singleton. On Windows
every image that links liblogos_protocol.a / liblogos_qt_sdk.a statically
gets its own copy of the code and therefore its own statics -- measured:
NINE images in the Basecamp payload each defined
TokenManager::instance()::instance. The host saved a capability token
into its copy, the UI plugin read its own empty copy, and every
cross-module call was refused (29 "ModuleProxy: rejecting unauthorized
call").

LOGOS_SHARED_API marks the affected types. It expands to
__declspec(dllimport) only for a consumer that opts in with
LOGOS_SHARED_USE_DLL, and to nothing everywhere else -- off Windows, and
inside logos-protocol/logos-qt-sdk/liblogos_core themselves, so the
static archives compile byte-identically to before.

The dllimport is the load-bearing half, not the export: it rewrites the
reference to go through __imp_, so the plain symbol is never undefined
and GNU ld never pulls the archive member that would redefine it. Without
it the link still succeeds, binds to the archive, and gives no diagnostic
at all.

logos_shared_api.h records both wrong answers -- export everything
(collides with the static archive over LogosAPI) and export nothing
(today's silent per-image statics) -- so neither gets reinvented.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(windows): let checks and devShells take the arg forAllSystems now passes

The cross-target commit added `inherit system;` to forAllSystems so the Windows
arm could tell which target it was building, but left `checks` and `devShells`
on the strict `({ pkgs }: ...)` pattern.  A Nix attrset pattern without `...` is
exact, so both stopped evaluating:

    error: function 'anonymous lambda' called with unexpected argument 'system'

on EVERY platform, not just Windows -- `nix flake check` and `ws develop
logos-protocol` are dead on this branch while they work on master.  `packages`
was unaffected because it goes through forAllTargets, which is why nothing
caught it.

Measured, same worktree, before and after:
  before: checks.aarch64-darwin -> the error above at flake.nix:52
  after:  checks.aarch64-darwin -> [ "tests" ]
          devShells.aarch64-darwin.default.name -> "nix-shell"
          packages -> [ aarch64-darwin aarch64-linux x86_64-darwin x86_64-linux
                        x86_64-windows ]

* chore(deps): re-pin logos-nix to the merged Windows overlay

The cross overlay landed in logos-nix#2.  This branch was locked to a
pre-merge rev, which has no `lib.forAllTargets` and no `lib.mkWindowsPkgs`,
so it could not evaluate standalone -- only against the unmerged branch.

Level 2 of the Windows chain; L1 (logos-nix) is merged.

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 09:44:25 -03:00

196 lines
5.8 KiB
C++

#ifndef TOKEN_MANAGER_H
#define TOKEN_MANAGER_H
#include <QObject>
#include <QString>
#include <QHash>
#include <QMutex>
#include <QByteArray>
#include <QCryptographicHash>
#include <string>
#include "logos_shared_api.h"
/**
* @brief Render a capability/auth token safe to write to logs.
*
* Tokens gate all cross-module RPC and are accepted by value (see
* ModuleProxy::isAuthorized), so a raw token recovered from a log line is
* directly replayable. This collapses a token to a non-reversible, non-
* replayable fingerprint — a fixed prefix plus the first bytes of its SHA-256 —
* suitable for correlating log lines without exposing the secret. The
* "redacted:" prefix signals to anyone reading the log that this is a
* deliberately non-replayable fingerprint, not a truncated real token. Empty
* tokens render as "<none>" so missing-token cases stay greppable.
*
* Always pass tokens through this before logging; never log the raw value.
*/
inline QString redactToken(const QString& token)
{
if (token.isEmpty()) {
return QStringLiteral("<none>");
}
const QByteArray digest =
QCryptographicHash::hash(token.toUtf8(), QCryptographicHash::Sha256);
return QStringLiteral("redacted:") + QString::fromLatin1(digest.toHex().left(8)) + QStringLiteral("…");
}
/**
* @brief TokenManager provides a singleton interface for managing authentication tokens
*
* This class manages a collection of tokens identified by keys, providing thread-safe
* access to store, retrieve, and manage tokens throughout the application lifecycle.
*
* LOGOS_SHARED_API: the singleton below is a function-local static, so it is one
* per copy of the code. On PE that means one per IMAGE unless the consumer
* imports it from liblogos_core.dll — which is exactly the token-invisibility
* bug described in logos_shared_api.h. Off Windows, and inside the provider,
* this expands to nothing.
*/
class LOGOS_SHARED_API TokenManager : public QObject
{
Q_OBJECT
public:
/**
* @brief Get the singleton instance of TokenManager
* @return TokenManager& Reference to the singleton instance
*/
static TokenManager& instance();
/**
* @brief Save a token with the given key
* @param key The identifier for the token
* @param token The token value to store
*/
void saveToken(const QString& key, const QString& token);
/**
* @brief Save a token — const char* overload (resolves ambiguity, delegates to QString)
*/
void saveToken(const char* key, const char* token)
{ saveToken(QString(key), QString(token)); }
/**
* @brief Save a token with the given key (std::string overload)
*/
void saveToken(const std::string& key, const std::string& token);
/**
* @brief Retrieve a token by key
* @param key The identifier for the token
* @return QString The token value, or empty string if not found
*/
QString getToken(const QString& key) const;
/**
* @brief Retrieve a token — const char* overload (resolves ambiguity, delegates to QString)
*/
QString getToken(const char* key) const
{ return getToken(QString(key)); }
/**
* @brief Retrieve a token by key (std::string overload)
* @return std::string The token value, or empty string if not found
*/
std::string getToken(const std::string& key) const;
/**
* @brief Check if a token exists for the given key
* @param key The identifier to check
* @return bool True if token exists, false otherwise
*/
bool hasToken(const QString& key) const;
/**
* @brief hasToken — const char* overload (resolves ambiguity, delegates to QString)
*/
bool hasToken(const char* key) const
{ return hasToken(QString(key)); }
/**
* @brief Check if a token exists for the given key (std::string overload)
*/
bool hasToken(const std::string& key) const;
/**
* @brief Remove a token by key
* @param key The identifier for the token to remove
* @return bool True if token was removed, false if it didn't exist
*/
bool removeToken(const QString& key);
/**
* @brief removeToken — const char* overload (resolves ambiguity, delegates to QString)
*/
bool removeToken(const char* key)
{ return removeToken(QString(key)); }
/**
* @brief Remove a token by key (std::string overload)
*/
bool removeToken(const std::string& key);
/**
* @brief Clear all tokens
*/
void clearAllTokens();
/**
* @brief Get all token keys
* @return QList<QString> List of all token keys
*/
QList<QString> getTokenKeys() const;
/**
* @brief Get the number of stored tokens
* @return int Number of tokens stored
*/
int tokenCount() const;
signals:
/**
* @brief Emitted when a token is saved
* @param key The key of the saved token
*/
void tokenSaved(const QString& key);
/**
* @brief Emitted when a token is removed
* @param key The key of the removed token
*/
void tokenRemoved(const QString& key);
/**
* @brief Emitted when all tokens are cleared
*/
void allTokensCleared();
private:
/**
* @brief Private constructor for singleton pattern
* @param parent Parent QObject
*/
explicit TokenManager(QObject *parent = nullptr);
/**
* @brief Private destructor
*/
~TokenManager();
// Delete copy constructor and assignment operator to enforce singleton
TokenManager(const TokenManager&) = delete;
TokenManager& operator=(const TokenManager&) = delete;
/**
* @brief Hash map storing tokens by key
*/
QHash<QString, QString> m_tokens;
/**
* @brief Mutex for thread-safe access to tokens
*/
mutable QMutex m_mutex;
};
#endif // TOKEN_MANAGER_H