Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
5e90825
config: add synced refund-requested flag to UserProfile
jagerman Jul 27, 2026
ac16b89
pro_backend: remove set_payment_refund_requested endpoint
jagerman Jul 28, 2026
f127f91
config: drop redundant condition in if-init statements
jagerman Jul 28, 2026
e95f574
config: ignore refund-requested values older than a week
jagerman Jul 28, 2026
e28af59
pro_backend: remove /add_pro_payment; redemption is now implicit
jagerman Jul 28, 2026
7c0fde4
config: migrate legacy millisecond pro_access_expiry on read
jagerman Jul 28, 2026
d48acb9
pro_backend: collapse redundant ProProofResponse base
jagerman Jul 28, 2026
470d7eb
config: stop persisting the proof version (dicts aren't atomic)
jagerman Jul 28, 2026
3029ea5
config: store the pro credential as one atomic bt-encoded value
jagerman Jul 28, 2026
8f516c2
pro: add ProProof::rotating_seed deterministic generator
jagerman Jul 28, 2026
ef91a66
config: add maybe_span dict helper
jagerman Jul 28, 2026
33c6dc9
config: add pro_prepaid (purchase-in-flight) flag + auto-clears
jagerman Jul 28, 2026
495b3e6
config: add UserProfile::pro_renewal_target (when to re-request a proof)
jagerman Jul 28, 2026
a831114
tests: use chrono duration literals for pro/refund timestamps
jagerman Jul 28, 2026
f10f1e8
pro_message: assert the previously-ignored to_chars ec
jagerman Jul 28, 2026
6c05257
config: use maybe_span instead of maybe_vector for pro/group key loads
jagerman Jul 29, 2026
77c15ff
session_protocol: drop the ProSignedMessage struct
jagerman Jul 29, 2026
0d6c0a5
pro_backend: drop the redundant public *_sig(s) helpers
jagerman Jul 29, 2026
bb388ba
session_protocol: make ProProof::status const
jagerman Jul 29, 2026
41c05ee
session_protocol: fix stale doc comments
jagerman Jul 29, 2026
3c9f266
session_protocol: use a cheap validly-encoded decoy signature
jagerman Jul 29, 2026
699f823
Fix unused parameters
jagerman Jul 29, 2026
cd93847
session_protocol: drop the unused sent_timestamp from encode_for_comm…
jagerman Jul 29, 2026
786551a
session_encrypt/quic: drop parameters missed in the unused-parameters…
jagerman Jul 29, 2026
d5bb6d1
session_protocol: rename ProProof::rotating_seed's timestamp param to…
jagerman Jul 29, 2026
fe277e0
config: defer pro_renewal_target briefly at a rotation boundary
jagerman Jul 29, 2026
0c6121b
attachments: keep is_final for the assert in process_chunk (fix debug…
jagerman Jul 29, 2026
58bafc4
session_protocol/config: apply review to the rotating-seed rename + r…
jagerman Jul 29, 2026
ba39bcf
util: view-based utf16_truncate/utf16_count; drop asserts on user input
jagerman Jul 30, 2026
a4809f7
session_protocol: pro_features_for_utf8/16 -> pro_features_for_message
jagerman Jul 30, 2026
88fbadd
session_protocol/user_profile: fix "weekly rotation" mischaracterizat…
jagerman Jul 30, 2026
c7d2aa8
user_profile: only schedule a proof fetch when there's an entitlement…
jagerman Jul 30, 2026
078c611
pro_backend: surface advisory account_expiry_ts from the proof response
jagerman Jul 30, 2026
af58b0c
session_protocol: document PRO_RENEWAL_LEAD as a locked backend contract
jagerman Jul 31, 2026
4da5198
formatting
jagerman Jul 31, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ else()
endif()

option(WARNINGS_AS_ERRORS "Treat all compiler warnings as errors" OFF)
option(WARN_UNUSED_PARAMETERS "Enabled unused parameter warnings" ON)

option(STATIC_LIBSTD "Statically link libstdc++/libgcc" ${default_static_libstd})

Expand Down
28 changes: 17 additions & 11 deletions include/session/config/pro.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,15 @@ namespace session::config {

/// keys used currently or in the past (so that we don't reuse):
///
/// s + session pro data
/// |
/// +-- p + proof
/// | |
/// | +-- @ - version
/// | +-- e - expiry unix timestamp (in milliseconds)
/// | +-- g - revocation_tag
/// | +-- s - proof signature, signed by the Session Pro Backend's ed25519 key
/// |
/// +-- r - rotating ed25519 seed (32b)
/// s - session pro credential: a single opaque, bt-encoded string (NOT a config sub-dict). Storing
/// it as one value is deliberate: config dicts merge per-key and non-atomically, so a proof
/// split across sibling keys could end up with its signature stitched onto a *different*
/// update's fields (or its seed desynced from the pubkey the sig authorizes). As one lone
/// string the whole credential moves as an indivisible unit. The bt-encoded dict inside is:
/// e - proof expiry unix timestamp (seconds)
/// g - revocation_tag (32 bytes)
/// r - rotating ed25519 seed (32 bytes); the rotating pubkey is derived from it
/// s - proof signature by the Session Pro Backend's ed25519 key (64 bytes)
class ProConfig {
public:
/// Rotating private key for the public key specified in the proof. On the wire we store the
Expand All @@ -27,7 +26,14 @@ class ProConfig {
/// A cryptographic proof for entitling an Ed25519 key to Session Pro
ProProof proof;

bool load(const dict& root);
/// Parse the credential from its bt-encoded config value (the "s" key). Returns false if the
/// value is empty, not well-formed bt, or missing/wrong-sized fields -- the caller treats that
/// as "no usable proof" (e.g. an older client's dict-shaped value, which self-heals on the next
/// proof fetch).
bool load(std::string_view bt_encoded);

/// Serialise the credential to the bt-encoded string stored at the "s" config key.
std::string serialize() const;

bool operator==(const ProConfig& other) const {
return rotating_privkey == other.rotating_privkey && proof == other.proof;
Expand Down
67 changes: 67 additions & 0 deletions include/session/config/user_profile.h
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,73 @@ LIBSESSION_EXPORT int64_t user_profile_get_pro_access_expiry(const config_object
LIBSESSION_EXPORT void user_profile_set_pro_access_expiry(
config_object* conf, int64_t access_expiry_ts);

/// API: user_profile/user_profile_get_refund_requested
///
/// Retrieves the timestamp at which the user requested a refund of their current Session Pro
/// subscription. This state is synced across the user's devices via config (not the Pro backend).
/// A stored value more than a week in the past is ignored (returns 0).
///
/// Inputs:
/// - `conf` -- [in] Pointer to the config object
///
/// Outputs:
/// - `int64_t` - the unix timestamp (seconds) at which a refund was requested, or 0 if no refund
/// has been requested (or the stored value is stale).
LIBSESSION_EXPORT int64_t user_profile_get_refund_requested(const config_object* conf);

/// API: user_profile/user_profile_set_refund_requested
///
/// Records (or clears) that the user has requested a refund of their current Session Pro
/// subscription, propagating it to the user's other devices via config sync. The client should
/// clear it (passing 0) when a new subscription begins.
///
/// Inputs:
/// - `conf` -- [in] Pointer to the config object
/// - `refund_ts` -- the timestamp (unix epoch seconds) at which the refund was requested, or 0 to
/// clear the refund-requested state.
LIBSESSION_EXPORT void user_profile_set_refund_requested(config_object* conf, int64_t refund_ts);

/// API: user_profile/user_profile_get_pro_prepaid
///
/// Retrieves the timestamp at which a Session Pro purchase was initiated (the "purchase in flight"
/// marker), synced across the user's devices. A stored value more than a week in the past is
/// ignored (returns 0).
///
/// Inputs:
/// - `conf` -- [in] Pointer to the config object
///
/// Outputs:
/// - `int64_t` - the unix timestamp (seconds) at which a purchase was initiated, or 0 if none is
/// pending (or the stored value is stale).
LIBSESSION_EXPORT int64_t user_profile_get_pro_prepaid(const config_object* conf);

/// API: user_profile/user_profile_set_pro_prepaid
///
/// Records (or clears) that a Session Pro purchase is in flight so the user's other devices poll
/// the backend to pull the entitlement through. A no-op if the account is already Pro; cleared
/// automatically once entitlement lands, or explicitly by passing 0.
///
/// Inputs:
/// - `conf` -- [in] Pointer to the config object
/// - `prepaid_ts` -- the timestamp (unix epoch seconds) at which the purchase was initiated, or 0
/// to clear the marker.
LIBSESSION_EXPORT void user_profile_set_pro_prepaid(config_object* conf, int64_t prepaid_ts);

/// API: user_profile/user_profile_get_pro_renewal_target
///
/// Decide when to (re)request a Session Pro proof (see the C++ pro_renewal_target). Given `now`,
/// returns the unix timestamp (seconds) at which a renewal should be attempted -- renew now if it
/// is <= now, otherwise schedule for then -- or 0 if no renewal is needed.
///
/// Inputs:
/// - `conf` -- [in] Pointer to the config object
/// - `now` -- the caller's current unix timestamp (seconds)
///
/// Outputs:
/// - `int64_t` - the renewal-target unix timestamp, or 0 for "no renewal needed".
LIBSESSION_EXPORT int64_t
user_profile_get_pro_renewal_target(const config_object* conf, int64_t now);

#ifdef __cplusplus
} // extern "C"
#endif
93 changes: 91 additions & 2 deletions include/session/config/user_profile.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,21 @@ using namespace std::literals;
/// omitted if the setting has not been explicitly set (or has been explicitly cleared for some
/// reason).
/// f - session pro features bitset
/// s - session pro credential (rotating seed + proof) as a single opaque bt-encoded string, stored
/// as one value so it merges atomically; see config/pro.hpp.
/// t - The unix timestamp (seconds) that the user last explicitly updated their profile information
/// (automatically updates when changing `name`, `profile_pic` or `set_blinded_msgreqs`).
/// E - user pro access expiry unix timestamp (in milliseconds). Note: This can be different from
/// the pro proof expiry which can be sooner.
/// E - user pro access expiry unix timestamp (in seconds). Note: This can be different from the pro
/// proof expiry which can be sooner. (Some (unreleased) testing clients stored this in
/// milliseconds; the getter migrates any such value on read -- see get_pro_access_expiry.)
/// R - unix timestamp (seconds) at which the user requested a refund of their current Session Pro
/// subscription, for cross-device sync of the refund-requested state. Omitted when no refund
/// has been requested; cleared by the client when a new subscription begins. Values more than
/// a week in the past are ignored on read (treated as if unset).
/// I - unix timestamp (seconds) at which a Session Pro purchase was initiated ("purchase in
/// flight"), so all the account's devices poll the backend to pull the entitlement through.
/// Inserted only when not already pro; cleared automatically when entitlement lands; values
/// more than a week in the past are ignored on read.
/// P - user profile url after re-uploading (should take precedence over `p` when `T > t`).
/// Q - user profile decryption key (binary) after re-uploading (should take precedence over `q`
/// when `T > t`).
Expand Down Expand Up @@ -327,6 +338,84 @@ class UserProfile : public ConfigBase {
/// - `access_expiry_ts` -- The timestamp (unix epoch seconds) that the users Session Pro access
/// will expire, or nullopt to remove the value.
void set_pro_access_expiry(std::optional<sys_seconds> access_expiry_ts);

/// API: user_profile/UserProfile::get_refund_requested
///
/// Retrieves the timestamp at which the user requested a refund of their current Session Pro
/// subscription, if any. This is synced across the user's devices so that a refund requested
/// on one device is reflected on the others; it does not go through the Pro backend.
///
/// A stored value more than a week in the past is ignored (returns nullopt), so that a
/// refund-requested flag some client neglected to clear cannot linger indefinitely.
///
/// Inputs: None
///
/// Outputs:
/// - `std::optional<sys_seconds>` - the unix timestamp (seconds) at which a refund was
/// requested, or nullopt if no refund has been requested (or the stored value is stale).
std::optional<sys_seconds> get_refund_requested() const;

/// API: user_profile/UserProfile::set_refund_requested
///
/// Records (or clears) that the user has requested a refund of their current Session Pro
/// subscription. Setting this propagates the refund-requested state to the user's other
/// devices via config sync. The client is responsible for clearing it (passing nullopt) when a
/// new subscription begins so a future subscription does not inherit a stale value.
///
/// Inputs:
/// - `when` -- the timestamp (unix epoch seconds) at which the refund was requested, or nullopt
/// to clear the refund-requested state.
void set_refund_requested(std::optional<sys_seconds> when);

/// API: user_profile/UserProfile::get_pro_prepaid
///
/// Retrieves the timestamp at which a Session Pro purchase was initiated (the "purchase in
/// flight" marker), synced across the user's devices so that any device can drive the backend
/// redemption to completion. A stored value more than a week in the past is ignored (returns
/// nullopt) so a purchase that never propagated doesn't make devices poll forever.
///
/// Inputs: None
///
/// Outputs:
/// - `std::optional<sys_seconds>` - the unix timestamp (seconds) at which a purchase was
/// initiated, or nullopt if none is pending (or the stored value is stale).
std::optional<sys_seconds> get_pro_prepaid() const;

/// API: user_profile/UserProfile::set_pro_prepaid
///
/// Records (or clears) that a Session Pro purchase is in flight, propagating it to the user's
/// other devices so they poll the backend to pull the new entitlement through. Setting is a
/// no-op if the account is already entitled to Pro (there would be nothing to poll for); it is
/// otherwise cleared automatically once entitlement lands (see set_pro_config /
/// set_pro_access_expiry), or explicitly by passing nullopt.
///
/// Inputs:
/// - `when` -- the timestamp (unix epoch seconds) at which the purchase was initiated, or
/// nullopt to clear the marker.
void set_pro_prepaid(std::optional<sys_seconds> when);

/// API: user_profile/UserProfile::pro_renewal_target
///
/// Decide when the client should (re)request a Session Pro proof, centralising logic clients
/// previously each implemented (and got inconsistently wrong). Returns the timestamp at which a
/// renewal should be attempted -- if it is <= the caller's clock, renew now; a future value can
/// be scheduled -- or nullopt when no renewal is needed. Given the stored proof and access
/// expiry:
/// - a present-but-expired proof -> `now` (always re-check with the backend; it may have
/// auto-renewed, and an authoritative not-Pro then clears the credential);
/// - no proof at all but a purchase in flight (prepaid marker) -> `now`;
/// - no proof and no purchase in flight -> nullopt (the account isn't Pro);
/// - a valid proof with access expiry still more than an hour ahead -> an hour before the
/// proof expires (preemptive), nudged off a rotating-seed period boundary so all devices
/// agree;
/// - otherwise (valid proof, entitlement ending or unknown) -> nullopt.
///
/// Inputs:
/// - `now` -- the caller's current time.
///
/// Outputs:
/// - `std::optional<sys_seconds>` - when to renew, or nullopt for "no renewal needed".
std::optional<sys_seconds> pro_renewal_target(sys_seconds now) const;
};

} // namespace session::config
2 changes: 1 addition & 1 deletion include/session/network/transport/network_transport.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class ITransport {
virtual void close_connections() = 0;

virtual ConnectionStatus get_status() const = 0;
virtual void set_node_failure_reporter(node_failure_reporter_t reporter) {}
virtual void set_node_failure_reporter(node_failure_reporter_t /*reporter*/) {}
virtual void verify_connectivity(
service_node node,
std::chrono::milliseconds timeout,
Expand Down
1 change: 0 additions & 1 deletion include/session/network/transport/quic_transport.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,6 @@ class QuicTransport : public ITransport, public std::enable_shared_from_this<Qui
void _fail_connection(
const std::string& address_pubkey_hex,
const std::string& initiating_req_id,
std::optional<oxen::quic::ConnectionID> conn_id,
std::optional<uint64_t> error_code,
std::optional<std::string> custom_error);
};
Expand Down
Loading