Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
28 changes: 20 additions & 8 deletions channels/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,22 @@ def _channel_auth_group_path():
return os.path.join(_MEMORY_DIRECTORY, _CHANNEL_DIR_NAME, _CHANNEL_AUTH_GROUP_FILE)


def _append_json_line(path, payload):
os.makedirs(os.path.dirname(path), exist_ok=True)
prefix = ""
try:
with open(path, "rb") as probe:
probe.seek(0, os.SEEK_END)
if probe.tell():
probe.seek(-1, os.SEEK_END)
if probe.read(1) != b"\n":
prefix = "\n"
except FileNotFoundError:
pass
with open(path, "a", encoding="utf-8") as f:
f.write(prefix + json.dumps(payload, separators=(",", ":")) + "\n")


def load_channel_auth_state(channel_identifier):
"""Validate and load one channel's persisted owner and active groups."""
channel_identifier = str(channel_identifier or "").strip()
Expand All @@ -187,7 +203,7 @@ def load_channel_auth_state(channel_identifier):

def read_records(path, label):
try:
with open(path, "r", encoding="utf-8") as source:
with open(path, "r", encoding="utf-8", errors="replace") as source:
records = []
for line_number, line in enumerate(source, 1):
if not line.strip():
Expand Down Expand Up @@ -263,10 +279,8 @@ def store_channel_authenticated_group_id(channel_identifier, group_id, authorize
"authorized_by": authorized_by_user_id,
}
path = _channel_auth_group_path()
os.makedirs(os.path.dirname(path), exist_ok=True)
try:
with open(path, "a", encoding="utf-8") as f:
f.write(json.dumps(payload, separators=(",", ":")) + "\n")
_append_json_line(path, payload)
except OSError as e:
raise RuntimeError("Failed to write channel authenticated group record") from e
return True
Expand All @@ -284,7 +298,7 @@ def get_channel_saved_group_id(channel_identifier, group_id):

authorized = False
try:
with open(_channel_auth_group_path(), "r", encoding="utf-8") as f:
with open(_channel_auth_group_path(), "r", encoding="utf-8", errors="replace") as f:
for line in f:
try:
record = json.loads(line)
Expand Down Expand Up @@ -354,10 +368,8 @@ def revoke_channel_group(channel_identifier, group_id, requester_user_id):
"revoked": True,
}
path = _channel_auth_group_path()
os.makedirs(os.path.dirname(path), exist_ok=True)
try:
with open(path, "a", encoding="utf-8") as f:
f.write(json.dumps(payload, separators=(",", ":")) + "\n")
_append_json_line(path, payload)
except OSError as e:
raise RuntimeError("Failed to write channel group revocation record") from e

Expand Down
31 changes: 31 additions & 0 deletions tests/test_auth_standalone.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,3 +190,34 @@ def test_load_channel_auth_state_skips_malformed_records(
"Skipping malformed channel authenticated group record at line 2" in warning
for warning in warnings
)


def test_load_channel_auth_state_skips_undecodable_bytes(monkeypatch, tmp_path):
auth = load_auth_module(monkeypatch)
monkeypatch.setattr(auth, "_MEMORY_DIRECTORY", str(tmp_path))

assert auth.store_channel_authenticated_user_id("TELEGRAM", "owner") is True
assert auth.store_channel_authenticated_group_id(
"TELEGRAM", "active", "owner"
) is True
path = tmp_path / ".channel" / "authenticated-group.json"
with path.open("ab") as target:
target.write(b"\xff\xfe garbage\n")

assert auth.load_channel_auth_state("TELEGRAM") == ("owner", {"active"})


def test_a_record_written_after_an_unterminated_one_is_kept(monkeypatch, tmp_path):
auth = load_auth_module(monkeypatch)
monkeypatch.setattr(auth, "_MEMORY_DIRECTORY", str(tmp_path))

assert auth.store_channel_authenticated_user_id("TELEGRAM", "owner") is True
assert auth.store_channel_authenticated_group_id(
"TELEGRAM", "active", "owner"
) is True
path = tmp_path / ".channel" / "authenticated-group.json"
with path.open("a", encoding="utf-8") as target:
target.write('{"time":"2026-09-02T10:00:00Z","channel_ident')

assert auth.revoke_channel_group("TELEGRAM", "active", "owner") == "group_unbound"
assert auth.load_channel_auth_state("TELEGRAM") == ("owner", set())
Loading