Prepare guarded reset-role transition and native recovery proof

Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a18e2d-f513-7742-b8e7-12af0b744ff3
This commit is contained in:
tegwick 2026-10-31 14:26:17 +01:00
parent 2387409d08
commit c07e6ebe34
8 changed files with 1027 additions and 0 deletions

View file

@ -0,0 +1,81 @@
{
"schema": "netkingdom.reset-role-proposal.v1",
"proposal_id": "NK-WP-0054-reset-authority-20261031-1",
"authority_status": "proposed_not_granted",
"endpoint": "https://lldap.coulomb.social",
"target": "svc-authelia",
"subject": "tegwick",
"before_roles": [
"lldap_strict_readonly"
],
"after_roles": [
"lldap_password_manager"
],
"runtime": {
"schema": "netkingdom.reset-runtime.v1",
"kube-system_uid": "a553c742-0115-43d4-99a4-a5ca56fe0786",
"sso_uid": "a4a148d8-92ab-41e6-b975-4e67b73edfc0",
"authelia": {
"uid": "dc40a925-2306-4a3b-89c0-e714187503c3",
"images": [
"docker.io/authelia/authelia@sha256:bd97cff4fcbf715b5ff1f9ae286afbe6033afce385302520b0368122d43a6f54"
],
"ready": true
},
"lldap": {
"uid": "743a86bd-5cb0-4ca0-9c56-a3438f4746a8",
"images": [
"docker.io/lldap/lldap@sha256:9e605a66c02514bfcffd1b67cafb1e98d50992216bb2871d7ae44622047dd09d"
],
"ready": true
},
"authelia_configuration": {
"uid": "7af1fa0d-1bd4-47e1-8873-0f620788ef3f",
"sha256": "0a8bca50641a4c6f27830f476c2c9f413fc3795711becccb8344ee4d6c376e80",
"bind_is_svc_authelia": true
}
},
"scope": {
"change": "Replace only svc-authelia strict-readonly membership with password-manager membership.",
"password_reach": "Every non-LLDAP-admin directory identity, including non-admin service and application-admin accounts; not limited to Coulomb.",
"excluded": "LLDAP administrators; tested user/group creation, other-profile edits and membership changes remain refused.",
"unchanged": "Credentials, service username, Kubernetes resources, Authelia account filter, MFA policy and all other directory memberships.",
"rollback": "Restore only svc-authelia strict-readonly role; retain any user password changes and unrelated directory updates. Existing HTTP tokens are not revoked: a token issued with password-manager rights can retain them until expiry (up to 24 hours), or an owner-run token revocation.",
"interruption": "Removing the old role precedes adding the new one. Brief lookup failure is possible. Uncertain attempts require explicit attended recovery; never blindly repeat grant.",
"acceptance": "Role postflight is not a user reset, app login, MFA or fresh-backup restore verdict.",
"self_profile": "Both built-in roles permit own-profile/password operations; this proposal does not claim an exhaustive denial of every directory write.",
"recovery_runtime": "Recovery tolerates unready workloads but still requires the reviewed namespace/deployment identities, images and configuration.",
"follow_through": "After a verified grant, align the service declaration and bind verification expectations; obtain the personal reset/app/MFA verdict and fresh identity backup/restore proof under NK-WP-0054-T16."
},
"proposed_service_identity": {
"principal_type": "SERVICE",
"conventions": [],
"purpose": "Authelia directory lookup and user-requested password recovery",
"grants": [
{
"mode": "USE",
"scope": "directory",
"read_only": true,
"activation": "STANDING",
"environments": [
"PRODUCTION"
]
},
{
"mode": "ADMINISTER",
"scope": "directory/passwords/non-lldap-admin",
"activation": "STANDING",
"environments": [
"PRODUCTION"
],
"note": "Native lldap_password_manager; includes non-admin service identities and application administrators."
}
],
"consumer": "authelia",
"owner_confirmed": false
},
"live_declaration_updated": false,
"new_resources": 0,
"credentials_rotated": false,
"requires_founder_admission": true
}

View file

@ -0,0 +1,398 @@
#!/usr/bin/env python3
"""Fixed-target, attended reset authority transition; preparation never grants access."""
import copy
import fcntl
import getpass
import hashlib
import importlib.util
import json
import os
import shlex
import stat
import subprocess
import sys
from datetime import UTC, datetime
from pathlib import Path
from types import SimpleNamespace
HERE = Path(__file__).resolve().parent
ROOT = HERE.parents[2]
SPEC = importlib.util.spec_from_file_location('reset_role_observer', HERE / 'reset-authority-observe.py')
observer = importlib.util.module_from_spec(SPEC)
SPEC.loader.exec_module(observer)
OLD = 'lldap_strict_readonly'
NEW = 'lldap_password_manager'
TARGET = 'svc-authelia'
SUBJECT = 'tegwick'
ENDPOINT = 'https://lldap.coulomb.social'
PLAN = HERE / 'reset-role-plan.json'
RUNTIME_CODE = '''import json,subprocess,hashlib,re
def get(kind,name,namespace):
args=['k3s','kubectl','get',kind,name,'-o','json']
if namespace:args+=['-n',namespace]
r=subprocess.run(args,capture_output=True,check=True)
return json.loads(r.stdout)
out={'schema':'netkingdom.reset-runtime.v1'}
for name in ['kube-system','sso']:
out[name+'_uid']=get('namespace',name,None)['metadata']['uid']
for name in ['authelia','lldap']:
d=get('deployment',name,'sso');s=d['status'];m=d['metadata']
out[name]={'uid':m['uid'],'images':[x['image'] for x in d['spec']['template']['spec']['containers']],
'ready':s.get('observedGeneration')==m['generation'] and s.get('readyReplicas')==d['spec'].get('replicas',1)}
c=get('configmap','authelia-config','sso');raw=c['data']['configuration.yml']
out['authelia_configuration']={'uid':c['metadata']['uid'],'sha256':hashlib.sha256(raw.encode()).hexdigest(),
'bind_is_svc_authelia':bool(re.search(r'^\\s+user:\\s*uid=svc-authelia,ou=people,dc=netkingdom,dc=local\\s*$',raw,re.M))}
print(json.dumps(out))
'''
class Refused(Exception):
pass
def canonical(value):
return json.dumps(value, sort_keys=True, separators=(',', ':')).encode()
def digest(value):
return hashlib.sha256(canonical(value)).hexdigest()
def observe_runtime():
result = subprocess.run(['ssh', 'railiance01', 'sudo -n python3 -c ' + shlex.quote(RUNTIME_CODE)],
capture_output=True, timeout=45, check=False)
if result.returncode:
raise Refused('runtime_observation_unavailable')
return json.loads(result.stdout)
def verify_runtime(expected, *, recovering=False):
actual = observe_runtime()
expected = copy.deepcopy(expected)
if recovering:
# Recovery must remain possible during an outage; identities/images/config stay pinned.
for component in ('authelia', 'lldap'):
actual.get(component, {}).pop('ready', None)
expected.get(component, {}).pop('ready', None)
if actual != expected:
raise Refused('reviewed_runtime_changed')
def proposal():
value = json.loads(PLAN.read_bytes())
if (value.get('target') != TARGET or value.get('subject') != SUBJECT
or value.get('endpoint') != ENDPOINT or value.get('before_roles') != [OLD]
or value.get('after_roles') != [NEW]):
raise Refused('fixed_reviewed_scope_required')
paths = [Path(__file__), *observer.SOURCES,
ROOT / 'docs/evidence/2026-10-31-reset-authority/current-roles.json',
ROOT / 'docs/evidence/2026-10-31-reset-authority/native-matrix.json',
ROOT / 'docs/evidence/2026-10-31-reset-authority/transition-validation.json']
value['sources'] = {str(p.relative_to(ROOT)): hashlib.sha256(p.read_bytes()).hexdigest()
for p in paths}
return value, 'sha256:' + digest(value)
class Directory:
def __init__(self, endpoint, password, opener=None):
self.reader = observer.Observer(endpoint, opener)
body = self.reader.post('/auth/simple/login', {'username': 'admin', 'password': password})
self.token = body.get('token') if isinstance(body, dict) else None
if (not isinstance(self.token, str) or not 1 <= len(self.token) <= 16384
or any(not 33 <= ord(c) <= 126 for c in self.token)):
raise Refused('human_admin_authentication_required')
def gql(self, token, query, variables):
value = self.reader.post('/api/graphql', {'query': query, 'variables': variables}, token)
if not isinstance(value, dict) or value.get('errors') or not isinstance(value.get('data'), dict):
raise Refused('directory_request_refused')
return value['data']
def snapshot(self):
return observer.transport.snapshot(SimpleNamespace(_gql=self.gql), self.token)
def membership(self, action, group_id):
if action not in {'add', 'remove'} or type(group_id) is not int:
raise Refused('fixed_membership_operation_required')
operation = 'addUserToGroup' if action == 'add' else 'removeUserFromGroup'
result = self.gql(self.token, 'mutation($u:String!,$g:Int!){' + operation
+ '(userId:$u,groupId:$g){ok}}', {'u': TARGET, 'g': group_id})
if result != {operation: {'ok': True}}:
raise Refused('membership_result_unverified')
def memberships(state, user):
if user not in state['users']:
raise Refused('required_subject_missing')
return sorted(g for g, value in state['groups'].items() if user in value['members'])
def validate(state, granting=True):
for name in (OLD, NEW, 'lldap_admin'):
if name not in state['groups']:
raise Refused('builtin_group_missing')
if 'lldap_admin' not in memberships(state, 'admin'):
raise Refused('human_admin_role_required')
if granting and 'lldap_admin' in memberships(state, SUBJECT):
raise Refused('accepted_subject_now_directory_admin')
if memberships(state, TARGET) not in ([], [OLD], [NEW]):
raise Refused('unexpected_target_membership')
def expected_state(state, target_group):
result = copy.deepcopy(state)
for name in (OLD, NEW):
members = result['groups'][name]['members']
if TARGET in members:
members.remove(TARGET)
if name == target_group:
members.append(TARGET)
members.sort()
return result
class Journal:
"""Private operator state, with an exclusive lock and durable write-before-effect."""
def __init__(self, path):
self.path = Path(os.path.abspath(Path(path).expanduser()))
if any(p.is_symlink() for p in (self.path, *self.path.parents)):
raise Refused('state_symlink_refused')
if any((p / '.git').exists() for p in (self.path, *self.path.parents)):
raise Refused('state_must_be_outside_repository')
parent = self.path.parent.stat()
if parent.st_uid != os.getuid() or parent.st_mode & 0o077:
raise Refused('private_owned_state_parent_required')
self.path.mkdir(mode=0o700, exist_ok=True)
info = self.path.stat()
if not stat.S_ISDIR(info.st_mode) or info.st_uid != os.getuid() or info.st_mode & 0o077:
raise Refused('private_owned_state_required')
fd = os.open(self.path / 'execute.lock', os.O_RDWR | os.O_CREAT | os.O_NOFOLLOW, 0o600)
info = os.fstat(fd)
if not stat.S_ISREG(info.st_mode) or info.st_uid != os.getuid() or info.st_mode & 0o077 or info.st_nlink != 1:
os.close(fd)
raise Refused('private_single_link_lock_required')
self.lock = os.fdopen(fd, 'r+')
try:
fcntl.flock(self.lock, fcntl.LOCK_EX | fcntl.LOCK_NB)
except OSError:
self.lock.close()
raise Refused('another_transition_is_active') from None
def close(self):
self.lock.close()
def read(self):
path = self.path / 'journal.json'
if path.is_symlink():
raise Refused('journal_symlink_refused')
if not path.exists():
return None
info = path.lstat()
if (not stat.S_ISREG(info.st_mode) or info.st_uid != os.getuid()
or info.st_mode & 0o077 or info.st_nlink != 1):
raise Refused('private_single_link_journal_required')
if info.st_size > 65536:
raise Refused('oversized_journal')
return json.loads(path.read_bytes())
def archive(self, raw):
path = self.path / ('history-' + hashlib.sha256(raw).hexdigest() + '.json')
if path.exists() or path.is_symlink():
info = path.lstat()
if (not stat.S_ISREG(info.st_mode) or info.st_uid != os.getuid()
or info.st_mode & 0o077 or info.st_nlink != 1 or path.read_bytes() != raw):
raise Refused('history_integrity_failure')
return
fd = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o600)
with os.fdopen(fd, 'wb') as stream:
stream.write(raw)
stream.flush()
os.fsync(stream.fileno())
def preserve_interrupted_write(self):
"""Recovery only: retain even a partial local write; never infer an LDAP result."""
path = self.path / 'journal.next'
if not path.exists() and not path.is_symlink():
return
info = path.lstat()
if (not stat.S_ISREG(info.st_mode) or info.st_uid != os.getuid()
or info.st_mode & 0o077 or info.st_nlink != 1 or info.st_size > 65536):
raise Refused('unsafe_interrupted_journal')
self.archive(path.read_bytes())
path.unlink()
def write(self, value):
prior = self.read()
if prior is not None:
self.archive((self.path / 'journal.json').read_bytes())
temporary = self.path / 'journal.next'
if temporary.exists() or temporary.is_symlink():
raise Refused('interrupted_journal_write_requires_review')
fd = os.open(temporary, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o600)
with os.fdopen(fd, 'wb') as stream:
stream.write(canonical(value) + b'\n')
stream.flush()
os.fsync(stream.fileno())
os.replace(temporary, self.path / 'journal.json')
fd = os.open(self.path, os.O_RDONLY | os.O_DIRECTORY)
try:
os.fsync(fd)
finally:
os.close(fd)
def transition(directory, journal, proposal_digest, operation):
if operation not in {'grant', 'rollback', 'recover'}:
raise Refused('fixed_transition_required')
prior = journal.read()
if operation == 'grant' and prior is not None:
raise Refused('existing_attempt_requires_review_not_replay')
if operation == 'recover' and prior is None and (journal.path / 'journal.next').exists():
before = directory.snapshot()
validate(before, granting=False)
if memberships(before, TARGET) != [OLD]:
raise Refused('orphan_write_requires_exact_readonly_state')
journal.preserve_interrupted_write()
if directory.snapshot() != before:
raise Refused('directory_changed_during_orphan_review')
record = {'schema': 'netkingdom.reset-role-transition.v1',
'proposal_digest': proposal_digest, 'target': TARGET, 'operation': operation,
'status': 'read_only_role_restored_verified', 'orphan_write_preserved': True,
'group_ids': {name: before['groups'][name]['id'] for name in (OLD, NEW)},
'before_sha256': digest(before), 'after_sha256': digest(before),
'password_reset_performed': False, 'credentials_changed': False,
'other_directory_memberships_unchanged': True,
'completed_at': datetime.now(UTC).isoformat()}
journal.write(record)
return record
if operation != 'grant':
if (not isinstance(prior, dict) or prior.get('proposal_digest') != proposal_digest
or prior.get('target') != TARGET
or prior.get('schema') != 'netkingdom.reset-role-transition.v1'
or prior.get('status') not in {
'prepared', 'remove_intent', 'add_intent',
'uncertain_requires_attended_recovery', 'password_manager_role_verified',
'read_only_role_restored_verified'}):
raise Refused('matching_prior_attempt_required')
if prior.get('status') == 'read_only_role_restored_verified':
raise Refused('completed_rollback_cannot_replay')
if operation == 'rollback' and prior.get('status') != 'password_manager_role_verified':
raise Refused('use_recover_for_interrupted_attempt')
if operation == 'recover' and prior.get('status') == 'password_manager_role_verified':
raise Refused('use_rollback_for_completed_grant')
before = directory.snapshot()
validate(before, granting=operation == 'grant')
current = memberships(before, TARGET)
if operation == 'grant' and current != [OLD]:
raise Refused('exact_read_only_baseline_required')
if operation == 'rollback' and current != [NEW]:
raise Refused('exact_granted_baseline_required')
ids = {name: before['groups'][name]['id'] for name in (OLD, NEW)}
if prior and prior.get('group_ids') != ids:
raise Refused('builtin_group_identity_changed')
if operation == 'recover':
journal.preserve_interrupted_write()
desired = NEW if operation == 'grant' else OLD
expected = expected_state(before, desired)
record = {'schema': 'netkingdom.reset-role-transition.v1', 'proposal_digest': proposal_digest,
'target': TARGET, 'operation': operation, 'status': 'prepared', 'group_ids': ids,
'started_at': datetime.now(UTC).isoformat(), 'before_sha256': digest(before),
'expected_after_sha256': digest(expected),
'prior_attempt_sha256': digest(prior) if prior else None,
'password_reset_performed': False, 'credentials_changed': False,
'other_directory_memberships_unchanged': False}
journal.write(record)
try:
# There is briefly no elevated read role; never introduce an untested role union.
for role in current:
if role != desired:
record.update(status='remove_intent', role=role)
journal.write(record)
directory.membership('remove', ids[role])
middle = expected_state(before, None)
if directory.snapshot() != middle:
raise Refused('intermediate_directory_drift')
if current != [desired]:
record.update(status='add_intent', role=desired)
journal.write(record)
directory.membership('add', ids[desired])
after = directory.snapshot()
if after != expected:
raise Refused('postflight_directory_drift')
record.update(status=('password_manager_role_verified' if operation == 'grant'
else 'read_only_role_restored_verified'),
completed_at=datetime.now(UTC).isoformat(), after_sha256=digest(after),
other_directory_memberships_unchanged=True)
journal.write(record)
return record
except Exception: # noqa: BLE001 -- retain an uncertain receipt, never leak provider bodies.
record.update(status='uncertain_requires_attended_recovery')
journal.write(record)
raise Refused('attempt_uncertain_use_reviewed_recovery') from None
def main(argv=None):
parser = observer.Parser(description=__doc__)
choices = parser.add_mutually_exclusive_group()
for mode in ('preflight', 'execute', 'rollback', 'recover', 'observe-runtime'):
choices.add_argument('--' + mode, action='store_true')
parser.add_argument('--expect-digest')
journal = None
try:
args = parser.parse_args(argv)
if args.observe_runtime:
print(json.dumps(observe_runtime(), indent=2))
return 0
plan, proposal_digest = proposal()
if args.expect_digest and args.expect_digest != proposal_digest:
raise Refused('reviewed_proposal_changed')
if not any((args.preflight, args.execute, args.rollback, args.recover)):
print(json.dumps({'status': 'prepared', 'proposal_digest': proposal_digest,
'scope': plan['scope'], 'live_changes': False}, indent=2))
return 0
if (not sys.stdin.isatty() or not sys.stderr.isatty()
or args.expect_digest != proposal_digest):
raise Refused('human_terminal_and_exact_digest_required')
verify_runtime(plan['runtime'], recovering=args.rollback or args.recover)
if not args.preflight:
path = Path.home() / '.local/state/net-kingdom/reset-authority' / proposal_digest[7:]
journal = Journal(path)
if args.execute and journal.read() is not None:
raise Refused('existing_attempt_requires_review_not_replay')
password = getpass.getpass('Password of the human admin account (hidden): ')
directory = Directory(ENDPOINT, password)
before = directory.snapshot()
operation = 'grant' if args.execute else 'rollback' if args.rollback else 'recover'
validate(before, granting=args.preflight or operation == 'grant')
if args.preflight:
if memberships(before, TARGET) != [OLD]:
raise Refused('exact_read_only_baseline_required')
print(json.dumps({'status': 'reset_role_preflight_verified',
'proposal_digest': proposal_digest, 'before_sha256': digest(before),
'runtime_sha256': digest(plan['runtime']), 'live_changes': False}))
return 0
print(json.dumps({'proposal_digest': proposal_digest, 'operation': operation,
'scope': plan['scope'], 'before_sha256': digest(before)}, indent=2))
confirmation = operation.upper() + ' ' + proposal_digest
if input('Type ' + confirmation + ' to proceed: ').strip() != confirmation:
raise Refused('not_confirmed_no_membership_change')
verify_runtime(plan['runtime'], recovering=args.rollback or args.recover)
result = transition(directory, journal, proposal_digest, operation)
result['runtime_sha256'] = digest(plan['runtime'])
journal.write(result)
print(json.dumps(result, indent=2))
return 0
except Exception as error: # noqa: BLE001 -- private-input and provider errors must be redacted.
print(json.dumps({'status': 'transition_refused_or_incomplete',
'reason': str(error) if isinstance(error, Refused)
else 'transition_unavailable', 'password_reset_performed': False}),
file=sys.stderr)
return 1
finally:
if journal is not None:
journal.close()
if __name__ == '__main__':
raise SystemExit(main())

View file

@ -0,0 +1,117 @@
"""Real membership changes in disposable, synthetic LLDAP; no production access."""
import importlib.util
import json
import secrets
import subprocess
import sys
from pathlib import Path
import pytest
HERE = Path(__file__).resolve().parent
def load(name, path):
spec = importlib.util.spec_from_file_location(name, path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
fixture = load("transition_native_fixture", HERE / "test_bind_split.py")
lab = fixture.lab
pytestmark = fixture.pytestmark
controller = load("transition_native_controller", HERE / "reset-role-transition.py")
DIGEST = "sha256:" + "a" * 64
def prepare(lab):
_, endpoint, password, server = lab
token = server.login("admin", password)
server.create_user(token, "svc-authelia", [controller.OLD])
server.create_user(token, "tegwick")
server.create_user(token, "unrelated")
return controller.Directory(endpoint, password)
def test_native_grant_and_rollback_preserve_memberships(lab, tmp_path):
directory = prepare(lab)
before = directory.snapshot()
journal = controller.Journal(tmp_path / "state")
try:
granted = controller.transition(directory, journal, DIGEST, "grant")
assert granted["status"] == "password_manager_role_verified"
assert directory.snapshot() == controller.expected_state(before, controller.NEW)
restored = controller.transition(directory, journal, DIGEST, "rollback")
assert restored["status"] == "read_only_role_restored_verified"
assert directory.snapshot() == before
finally:
journal.close()
@pytest.mark.parametrize("crash_after", ["remove", "add"])
def test_native_process_death_recovered_without_replaying_grant(lab, tmp_path, crash_after):
directory = prepare(lab)
before = directory.snapshot()
path = tmp_path / "state"
# Synthetic credentials only in the private pipe; actual os._exit bypasses handlers.
code = """
import importlib.util,json,os,sys
from pathlib import Path
v=json.load(sys.stdin)
s=importlib.util.spec_from_file_location('controller',v['source'])
m=importlib.util.module_from_spec(s);s.loader.exec_module(m)
d=m.Directory(v['endpoint'],v['password'])
original=d.membership
def die(action,group):
original(action,group)
if action==v['crash_after']:os._exit(86)
d.membership=die
j=m.Journal(Path(v['state']))
m.transition(d,j,v['digest'],'grant')
"""
result = subprocess.run([sys.executable, "-c", code], input=json.dumps({
"source": str(HERE / "reset-role-transition.py"), "endpoint": lab[1],
"password": lab[2], "state": str(path), "digest": DIGEST,
"crash_after": crash_after}), text=True, capture_output=True, timeout=30, check=False)
assert result.returncode == 86
assert not result.stdout and not result.stderr
journal = controller.Journal(path)
try:
assert journal.read()["status"] == crash_after + "_intent"
assert controller.memberships(directory.snapshot(), controller.TARGET) == (
[] if crash_after == "remove" else [controller.NEW])
with pytest.raises(controller.Refused, match="existing_attempt"):
controller.transition(directory, journal, DIGEST, "grant")
restored = controller.transition(directory, journal, DIGEST, "recover")
assert restored["status"] == "read_only_role_restored_verified"
assert directory.snapshot() == before
assert list(path.glob("history-*.json"))
for file in path.iterdir():
assert file.stat().st_mode & 0o077 == 0
assert lab[2] not in file.read_text()
finally:
journal.close()
def test_native_role_rollback_does_not_revoke_existing_http_token(lab, tmp_path):
directory = prepare(lab)
server = lab[3]
bind_password, user_password = secrets.token_hex(24), secrets.token_hex(24)
server.set_password(directory.token, controller.TARGET, bind_password)
journal = controller.Journal(tmp_path / 'state')
try:
controller.transition(directory, journal, DIGEST, 'grant')
old_token = server.login(controller.TARGET, bind_password)
assert old_token
controller.transition(directory, journal, DIGEST, 'rollback')
fresh_token = server.login(controller.TARGET, bind_password)
assert fresh_token
# Measure the limitation, not a revocation claim: pre-rollback JWT retains its role.
server.set_password(old_token, 'tegwick', user_password)
assert server.login('tegwick', user_password)
with pytest.raises(RuntimeError):
server.set_password(fresh_token, 'tegwick', secrets.token_hex(24))
assert server.login('tegwick', user_password)
finally:
journal.close()