<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>SpaceShaman</title>
    <link>https://spaceshaman.github.io/</link>
    <description>Recent content on SpaceShaman</description>
    <generator>Hugo</generator>
    <language>en-US</language>
    <copyright>SpaceShaman</copyright>
    <lastBuildDate>Fri, 21 Aug 2026 08:02:43 +0000</lastBuildDate>
    <atom:link href="https://spaceshaman.github.io/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Why I Replaced My Chair with a Meditation Cushion</title>
      <link>https://spaceshaman.github.io/posts/why-i-replaced-my-chair-with-a-meditation-cushion/</link>
      <pubDate>Fri, 21 Aug 2026 08:02:43 +0000</pubDate>
      <guid>https://spaceshaman.github.io/posts/why-i-replaced-my-chair-with-a-meditation-cushion/</guid>
      <description>A two-year experiment in replacing an office chair with a floor-level workspace.</description>
      <content:encoded><![CDATA[<h2 id="why">Why?</h2>
<p>I have been fascinated by Japanese culture for a very long time. One aspect that has always caught my attention is the longevity and overall health of Japanese people.</p>
<p>An interesting element of Japanese culture is that people spend a lot of time sitting on the floor. Most of you have probably seen scenes in Japanese movies where people sit on the floor around a low table.</p>
<p>This made me wonder whether sitting on the floor could be one of the factors contributing to their health and longevity, and whether I could apply this habit to my own life.</p>
<p>Every time you sit down on the floor and get back up, your body has to perform a physical movement. In a way, it is similar to doing a squat each time, and squats are very beneficial for the body. In addition, sitting in a meditation-like position encourages us to maintain better posture.</p>
<p>Since I had already been practicing meditation for a long time and owned a meditation cushion, I decided to run an experiment: I replaced the office chair I used for work with my meditation cushion.</p>
<p>I modified my desk so that its height would be suitable for working while sitting on the floor, and I began the experiment.</p>
<p><img alt="A floor-level workspace with a meditation cushion" loading="lazy" src="/images/floor-workspace.jpg"></p>
<h2 id="the-first-month">The First Month</h2>
<p>The first month was much more difficult than I expected. After the first few days, I was already close to giving up. My knees and back hurt, and despite my experience with meditation, I did not feel comfortable sitting on the floor for such a long time.</p>
<p>It quickly became clear that sitting on the floor for eight hours a day was very different from meditating for thirty minutes.</p>
<p>Despite the initial difficulties, I decided to continue the experiment and give my body enough time to adapt.</p>
<p>After the first month, the pain in my knees and back disappeared, and I started to feel much more comfortable. I also noticed a slight improvement in my posture.</p>
<h2 id="two-years-later-i-still-work-on-the-floor">Two Years Later, I Still Work on the Floor</h2>
<p>It has now been two years since I started this experiment, and I have no intention of going back to sitting in an office chair. I feel much better than I did before, my posture has improved significantly, and I can no longer imagine spending many hours sitting in a comfortable office chair—which, ironically, no longer feels comfortable to me at all.</p>
<p>I have also discovered that sitting on the floor allows us to use many different positions, which can also have a positive effect on the body. Spending the entire day in a single position is very unhealthy.</p>
<p>Yoga can be helpful here. Many asanas can be adapted to sitting and working at a computer, allowing us to regularly change the position in which we work.</p>
<p><strong>What do you think about this kind of workspace? Would you consider replacing your chair with a floor setup? Or maybe you have your own unusual way of working at a computer that makes you feel more comfortable or helps you stay active?</strong></p>
<p><em>Originally published on <a href="https://coderlegion.com/25062/why-i-replaced-my-chair-with-a-meditation-cushion">CoderLegion</a>.</em></p>
]]></content:encoded>
    </item>
    <item>
      <title>Evolving UserHarbor: From a Framework-Agnostic Core to Executable Storage Contracts</title>
      <link>https://spaceshaman.github.io/posts/building-userharbor-framework-agnostic-user-management-for-python/</link>
      <pubDate>Mon, 17 Aug 2026 12:31:04 +0000</pubDate>
      <guid>https://spaceshaman.github.io/posts/building-userharbor-framework-agnostic-user-management-for-python/</guid>
      <description>How UserHarbor evolved from a framework-independent core into an architecture backed by adapters and an executable storage contract.</description>
      <content:encoded><![CDATA[<p>User management is one of those problems that rarely feels difficult enough to deserve much attention.</p>
<p>Until you implement it for the fifth time.</p>
<p>Registration, login, sessions, email verification, password resets, password changes, account deletion, roles, permissions — none of these features are particularly unusual. But almost every application needs some combination of them, and the implementation often ends up tightly coupled to whatever framework, ORM, or infrastructure the project happened to use at the time.</p>
<p>That was the problem that led me to build <strong>UserHarbor</strong>.</p>
<p>UserHarbor is a framework-agnostic Python library for user account management. The goal is not to build another web framework or a complete identity platform. Instead, it provides a small domain-level API for common account operations while leaving HTTP, databases and email delivery to separate integrations.</p>
<p>Since I first wrote about the project, the interesting part has increasingly become not just the authentication API itself, but the boundary between the core and its integrations.</p>
<p><em>This article expands on <a href="/posts/i-built-userharbor-a-framework-agnostic-user-management-library-for-python/">my original introduction to UserHarbor</a>, focusing on how the architecture evolved as the project grew.</em></p>
<h2 id="the-problem-i-wanted-to-solve">The problem I wanted to solve</h2>
<p>Imagine building two applications.</p>
<p>One uses:</p>
<ul>
<li>FastAPI</li>
<li>SQLAlchemy</li>
<li>PostgreSQL</li>
<li>SMTP</li>
</ul>
<p>Another uses:</p>
<ul>
<li>Flask</li>
<li>MongoDB</li>
<li>an external email API</li>
</ul>
<p>The user-management rules are mostly the same.</p>
<p>A password still needs to be validated and hashed. A verification token still needs to expire. Password reset tokens still need to be protected. Sessions need to be created and invalidated. Roles and permissions need to be checked.</p>
<p>But in many libraries these rules are mixed together with database models, HTTP handlers or framework-specific abstractions.</p>
<p>I wanted the opposite.</p>
<p>The core should know <strong>what should happen</strong>, but not necessarily <strong>how the application stores or transports the data</strong>.</p>
<p>That leads to an architecture that looks roughly like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Application / Framework
</span></span><span class="line"><span class="cl">        │
</span></span><span class="line"><span class="cl">        ▼
</span></span><span class="line"><span class="cl">    UserHarbor
</span></span><span class="line"><span class="cl">        │
</span></span><span class="line"><span class="cl">        ├── UserStore
</span></span><span class="line"><span class="cl">        │       └── database / ORM / custom backend
</span></span><span class="line"><span class="cl">        │
</span></span><span class="line"><span class="cl">        └── EmailSender
</span></span><span class="line"><span class="cl">                └── SMTP / API / custom provider
</span></span></code></pre></div><p>The core owns things such as registration, validation, password hashing, token generation, token hashing, session handling and authorization rules.</p>
<p>The adapters own infrastructure.</p>
<h2 id="a-small-domain-level-api">A small domain-level API</h2>
<p>UserHarbor currently handles the common account lifecycle:</p>
<ul>
<li>user registration</li>
<li>email verification</li>
<li>login</li>
<li>sessions</li>
<li>logout from one or all sessions</li>
<li>password change</li>
<li>password reset</li>
<li>account deletion</li>
<li>roles and permissions</li>
</ul>
<p>It deliberately does not expose HTTP endpoints itself.</p>
<p>That means code using the core can look like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">user</span> <span class="o">=</span> <span class="n">harbor</span><span class="o">.</span><span class="n">register</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">username</span><span class="o">=</span><span class="s2">&#34;jane&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">email</span><span class="o">=</span><span class="s2">&#34;*Emails are not allowed*&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">password</span><span class="o">=</span><span class="s2">&#34;StrongPassword123!&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">harbor</span><span class="o">.</span><span class="n">verify_email</span><span class="p">(</span><span class="n">verification_token</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">session_token</span> <span class="o">=</span> <span class="n">harbor</span><span class="o">.</span><span class="n">login</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">username</span><span class="o">=</span><span class="s2">&#34;jane&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">password</span><span class="o">=</span><span class="s2">&#34;StrongPassword123!&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">current_user</span> <span class="o">=</span> <span class="n">harbor</span><span class="o">.</span><span class="n">get_current_user</span><span class="p">(</span><span class="n">session_token</span><span class="p">)</span>
</span></span></code></pre></div><p>The same <code>UserHarbor</code> instance can be used from FastAPI, Flask, Django, a CLI application or something that does not expose HTTP at all.</p>
<p>Authorization follows the same idea:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">harbor</span><span class="o">.</span><span class="n">roles</span><span class="o">.</span><span class="n">create</span><span class="p">(</span><span class="s2">&#34;admin&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">harbor</span><span class="o">.</span><span class="n">permissions</span><span class="o">.</span><span class="n">create</span><span class="p">(</span><span class="s2">&#34;users.delete&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">harbor</span><span class="o">.</span><span class="n">roles</span><span class="o">.</span><span class="n">grant_permission</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;admin&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;users.delete&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">harbor</span><span class="o">.</span><span class="n">grant_role</span><span class="p">(</span><span class="s2">&#34;jane&#34;</span><span class="p">,</span> <span class="s2">&#34;admin&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="n">harbor</span><span class="o">.</span><span class="n">has_permission</span><span class="p">(</span><span class="n">session_token</span><span class="p">,</span> <span class="s2">&#34;users.delete&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">delete_user</span><span class="p">()</span>
</span></span></code></pre></div><p>Or, if access should be enforced:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">user</span> <span class="o">=</span> <span class="n">harbor</span><span class="o">.</span><span class="n">require_permission</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">session_token</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;users.delete&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span></code></pre></div><p>UserHarbor implements simple role-based access control, but leaves application-specific authorization policy outside the core. It intentionally does not try to become a general policy engine.</p>
<h2 id="framework-agnostic-does-not-mean-framework-unfriendly">Framework-agnostic does not mean framework-unfriendly</h2>
<p>One thing I wanted to avoid was making framework independence come at the cost of developer experience.</p>
<p>For example, there is an official <code>userharbor-fastapi</code> integration.</p>
<p>Instead of manually writing authentication routes and dependencies, a FastAPI application can configure UserHarbor and attach the adapter:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">fastapi</span> <span class="kn">import</span> <span class="n">FastAPI</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">userharbor</span> <span class="kn">import</span> <span class="n">UserHarbor</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">userharbor_fastapi</span> <span class="kn">import</span> <span class="n">UserHarborFastAPI</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">harbor</span> <span class="o">=</span> <span class="n">UserHarbor</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">secret_key</span><span class="o">=</span><span class="s2">&#34;your-secret-key&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">store</span><span class="o">=</span><span class="n">store</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">email_sender</span><span class="o">=</span><span class="n">email_sender</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">auth</span> <span class="o">=</span> <span class="n">UserHarborFastAPI</span><span class="p">(</span><span class="n">harbor</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">app</span> <span class="o">=</span> <span class="n">FastAPI</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="n">app</span><span class="o">.</span><span class="n">include_router</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">auth</span><span class="o">.</span><span class="n">router</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">prefix</span><span class="o">=</span><span class="s2">&#34;/auth&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">tags</span><span class="o">=</span><span class="p">[</span><span class="s2">&#34;auth&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span></code></pre></div><p>The adapter provides the framework-specific layer: routers, request schemas, bearer authentication dependencies, error mapping and helpers for requiring roles or permissions.</p>
<p>The important part is that FastAPI still does not leak into the UserHarbor core.</p>
<p>You can replace the web framework without replacing the account-management logic.</p>
<h2 id="the-harder-problem-what-does-it-mean-to-implement-userstore">The harder problem: what does it mean to implement <code>UserStore</code>?</h2>
<p>Originally, separating persistence behind a <code>UserStore</code> interface seemed like the obvious solution.</p>
<p>Define an interface, implement the methods, and now SQLAlchemy, MongoDB, Redis or anything else can provide storage.</p>
<p>But there is a subtle problem.</p>
<p>Matching method signatures does not mean two storage implementations behave the same way.</p>
<p>Consider a password reset token.</p>
<p>Should creating a new token remove an older token?</p>
<p>What happens when a user is deleted?</p>
<p>Should their sessions disappear automatically?</p>
<p>What should happen if a transaction fails halfway through a password change?</p>
<p>Should deleting something that no longer exists raise an error?</p>
<p>These behaviors are part of the storage contract even though Python&rsquo;s type system cannot express them.</p>
<p>This became one of the most important changes in UserHarbor 0.7.0.</p>
<h2 id="turning-the-adapter-contract-into-executable-tests">Turning the adapter contract into executable tests</h2>
<p>UserHarbor now ships a reusable contract test suite for <code>UserStore</code> implementations.</p>
<p>An adapter can import the complete suite:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="c1"># tests/test_user_store_contract.py</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">userharbor.testing.user_store_contract</span> <span class="kn">import</span> <span class="o">*</span>
</span></span></code></pre></div><p>Then it only needs to provide a clean store:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">pytest</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@pytest.fixture</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">user_store</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="n">store</span> <span class="o">=</span> <span class="n">create_user_store</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">yield</span> <span class="n">store</span>
</span></span><span class="line"><span class="cl">    <span class="k">finally</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">dispose_user_store</span><span class="p">(</span><span class="n">store</span><span class="p">)</span>
</span></span></code></pre></div><p>The same tests can then run against SQLAlchemy, an in-memory implementation, or a completely different persistence backend.</p>
<p>Version 0.7.0 introduced <strong>57 reusable contract tests</strong> covering users, password hashes, verification tokens, password reset tokens, sessions, roles, permissions, relationships and transaction behavior.</p>
<p>This changes the meaning of an adapter.</p>
<p>A compatible <code>UserStore</code> no longer just claims to implement an interface. It can demonstrate that it follows the behavioral semantics expected by the core.</p>
<p>For example, the contract specifies that:</p>
<ul>
<li>usernames and email addresses are unique</li>
<li>user creation and the initial verification token are atomic</li>
<li>a new verification token replaces the previous one</li>
<li>a new password reset token replaces the previous one</li>
<li>deleting a user removes their sessions and related tokens</li>
<li>repeated relationship assignments are idempotent</li>
<li>deleting roles and permissions removes their assignments</li>
<li>successful transactions commit</li>
<li>failed transactions roll back</li>
<li>nested transactions participate in the outer transaction</li>
</ul>
<p>These details are easy to overlook when implementing another backend, and they are exactly the type of differences that can produce authentication bugs which only appear much later.</p>
<p>For me, this was an important step in the architecture: the abstraction is now described not only by Python protocols and documentation, but also by executable behavior.</p>
<h2 id="a-template-for-building-new-storage-adapters">A template for building new storage adapters</h2>
<p>To make that process easier, I also created <code>userharbor-inmemory</code>.</p>
<p>It is a minimal in-memory <code>UserStore</code> implementation that passes the complete storage contract.</p>
<p>It serves two purposes.</p>
<p>First, it is useful for tests and examples where a real database is unnecessary.</p>
<p>Second, the repository itself can be used as a template for creating new storage integrations. A developer can start with a working implementation and passing contract tests, replace the in-memory backend incrementally, and continuously verify that the new adapter still behaves correctly.</p>
<p>The idea is that creating something like a future database adapter should mostly become:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">working UserStore template
</span></span><span class="line"><span class="cl">        │
</span></span><span class="line"><span class="cl">        ▼
</span></span><span class="line"><span class="cl">replace persistence implementation
</span></span><span class="line"><span class="cl">        │
</span></span><span class="line"><span class="cl">        ▼
</span></span><span class="line"><span class="cl">run shared contract tests
</span></span><span class="line"><span class="cl">        │
</span></span><span class="line"><span class="cl">        ▼
</span></span><span class="line"><span class="cl">add backend-specific tests
</span></span></code></pre></div><p>instead of reverse-engineering the expected behavior from the core implementation.</p>
<h2 id="keeping-security-behavior-inside-the-core">Keeping security behavior inside the core</h2>
<p>Another important architectural boundary is that storage adapters never receive raw tokens for persistence.</p>
<p>UserHarbor generates the raw verification, reset and session tokens, but hashes them before passing them to <code>UserStore</code>.</p>
<p>The raw token is given only to the part of the application that needs it — for example, to an email sender or to the user after login.</p>
<p>The database stores the hash.</p>
<p>The core also owns behavior such as token expiration and session validation.</p>
<p>Sensitive account-discovery flows are designed to return neutral responses where appropriate. For example, requesting a password reset for an unknown email address does not reveal whether that account exists.</p>
<p>There are also account lifecycle notifications for events such as:</p>
<ul>
<li>successful email verification</li>
<li>password changes</li>
<li>password resets</li>
<li>account deletion</li>
</ul>
<p>The <code>EmailSender</code> interface decides how those messages are delivered, but it does not decide when the operation is valid. That decision remains in the core.</p>
<h2 id="sqlalchemy-without-owning-your-application">SQLAlchemy without owning your application</h2>
<p>The official SQLAlchemy adapter is useful out of the box, but one design requirement was that adopting UserHarbor should not force an application to adopt a completely separate user model.</p>
<p>By default, the adapter can manage its own user table.</p>
<p>But applications that already have a SQLAlchemy model can provide it instead:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">store</span> <span class="o">=</span> <span class="n">SQLAlchemyUserStore</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">SessionLocal</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">user_model</span><span class="o">=</span><span class="n">AppUser</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span></code></pre></div><p>Applications can also map their model to a richer public user object instead of being limited to UserHarbor&rsquo;s minimal representation.</p>
<p>The documentation now also covers using the adapter with Alembic migrations rather than relying on <code>metadata.create_all()</code> at application startup.</p>
<p>That distinction matters because examples should be easy to run, but real applications need a sensible path toward managing schema changes properly.</p>
<h2 id="installation">Installation</h2>
<p>The core can be installed on its own:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">pip install userharbor
</span></span></code></pre></div><p>Or with selected official integrations:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">pip install <span class="s2">&#34;userharbor[sqlalchemy,smtp,fastapi]&#34;</span>
</span></span></code></pre></div><p>For experimenting with the complete official stack:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">pip install <span class="s2">&#34;userharbor[all]&#34;</span>
</span></span></code></pre></div><p>The main official integrations currently include SQLAlchemy storage, SMTP email delivery and FastAPI support.</p>
<h2 id="what-i-deliberately-do-not-want-userharbor-to-become">What I deliberately do not want UserHarbor to become</h2>
<p>Feature creep is an easy trap for this kind of project.</p>
<p>Once you have authentication, it is tempting to add OAuth. Then social login. Then MFA. Organizations. Teams. ACLs. Resource ownership. Admin panels. A policy language.</p>
<p>Eventually the &ldquo;small authentication library&rdquo; becomes an application framework.</p>
<p>That is specifically what I am trying to avoid.</p>
<p>The core should remain focused on common account-management primitives.</p>
<p>More specialized functionality can exist as integrations or separate libraries if there is demand for it, but it should not make the basic package more complicated for everyone else.</p>
<p>One of the design principles of UserHarbor is therefore deliberately boring:</p>
<p><strong>stability is more important than feature count.</strong></p>
<p>Once the public API stabilizes, I would rather spend development effort on security, reliability, compatibility and performance than continuously expand the scope of the core.</p>
<h2 id="current-status">Current status</h2>
<p>UserHarbor is currently at version <strong>0.7.0</strong>.</p>
<p>The project is still young, and I do not consider the API stable or the library ready for production use yet.</p>
<p>At this stage, the project is as much about validating the architecture as adding functionality.</p>
<p>The areas I am particularly interested in getting feedback on are:</p>
<ul>
<li>the boundary between the core and integrations</li>
<li>the <code>UserStore</code> contract</li>
<li>the contract-testing approach</li>
<li>the FastAPI integration</li>
<li>custom storage backends</li>
<li>the shape of the public API</li>
<li>what should — and should not — belong in the core</li>
</ul>
<p>There are plenty of good framework-specific authentication libraries in Python.</p>
<p>UserHarbor is exploring a slightly different question:</p>
<p><strong>Can the account-management domain itself be reusable across frameworks and infrastructure, while integrations remain small, replaceable and independently testable?</strong></p>
<p>So far, I think that boundary is turning out to be the most interesting part of the project.</p>
<h2 id="links">Links</h2>
<p>Documentation:<br>
<a href="https://userharbor.github.io/userharbor/">https://userharbor.github.io/userharbor/</a></p>
<p>Repository:<br>
<a href="https://github.com/userharbor/userharbor">https://github.com/userharbor/userharbor</a></p>
<p>FastAPI integration:<br>
<a href="https://github.com/userharbor/userharbor-fastapi">https://github.com/userharbor/userharbor-fastapi</a></p>
<p>SQLAlchemy adapter:<br>
<a href="https://github.com/userharbor/userharbor-sqlalchemy">https://github.com/userharbor/userharbor-sqlalchemy</a></p>
<p>SMTP adapter:<br>
<a href="https://github.com/userharbor/userharbor-smtp">https://github.com/userharbor/userharbor-smtp</a></p>
<p><em>Originally published on <a href="https://coderlegion.com/24763/building-userharbor-framework-agnostic-user-management-for-python">CoderLegion</a>.</em></p>
]]></content:encoded>
    </item>
    <item>
      <title>I built UserHarbor — a framework-agnostic user management library for Python</title>
      <link>https://spaceshaman.github.io/posts/i-built-userharbor-a-framework-agnostic-user-management-library-for-python/</link>
      <pubDate>Wed, 01 Jul 2026 17:19:53 +0000</pubDate>
      <guid>https://spaceshaman.github.io/posts/i-built-userharbor-a-framework-agnostic-user-management-library-for-python/</guid>
      <description>Why I built UserHarbor and separated user-management logic from web frameworks, databases, ORMs and email providers.</description>
      <content:encoded><![CDATA[<p>While working on a SaaS application recently, I once again had to implement the same user account flow:</p>
<ul>
<li>registration</li>
<li>login</li>
<li>sessions</li>
<li>email verification</li>
<li>password reset</li>
<li>password change</li>
<li>account deletion</li>
<li>basic roles and permissions</li>
</ul>
<p>None of that was especially hard.</p>
<p>But it was repetitive.</p>
<p>I had written similar code before, and I did not want to keep rebuilding the same user-management boilerplate in every new Python project.</p>
<p>So I started working on <strong>UserHarbor</strong>.</p>
<h2 id="what-is-userharbor">What is UserHarbor?</h2>
<p><strong>UserHarbor</strong> is a framework-agnostic Python library for user account management.</p>
<p>The idea is simple:</p>
<blockquote>
<p>keep the core small, predictable, and independent from any specific web framework, database, ORM, or email provider.</p>
</blockquote>
<p>The core handles the account-management logic.
Integrations are handled by separate adapter packages.</p>
<p>So instead of building something only for FastAPI, Flask, Django, or one specific stack, I wanted a core that could be used in different kinds of Python applications.</p>
<p>For example:</p>
<ul>
<li>FastAPI apps</li>
<li>Flask apps</li>
<li>Django apps</li>
<li>CLI tools</li>
<li>internal tools</li>
<li>custom Python services</li>
</ul>
<h2 id="why-not-just-use-a-framework-specific-library">Why not just use a framework-specific library?</h2>
<p>There are already good tools for specific frameworks.</p>
<p>But I wanted something slightly different.</p>
<p>I did not want the user-management logic to be tightly coupled to:</p>
<ul>
<li>a web framework</li>
<li>a database layer</li>
<li>an email provider</li>
<li>a specific request/response model</li>
</ul>
<p>Instead, UserHarbor uses small interfaces for things like storage and email delivery.</p>
<p>The main interfaces are:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">UserStore</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="o">...</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">EmailSender</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="o">...</span>
</span></span></code></pre></div><p>The core does not care how users are stored or how emails are sent.</p>
<p>That part belongs to adapters.</p>
<h2 id="installation">Installation</h2>
<p>Install only the core package if you want to provide your own <code>UserStore</code> and <code>EmailSender</code> implementations:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">pip install userharbor
</span></span></code></pre></div><p>Install the core package with the official SQLAlchemy, SMTP, and FastAPI adapters:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">pip install <span class="s2">&#34;userharbor[sqlalchemy,smtp,fastapi]&#34;</span>
</span></span></code></pre></div><p>Or install all official integrations at once:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">pip install <span class="s2">&#34;userharbor[all]&#34;</span>
</span></span></code></pre></div><h2 id="official-adapters">Official adapters</h2>
<p>At the moment, there are a few official adapter packages:</p>
<ul>
<li><code>userharbor-sqlalchemy</code> — SQLAlchemy storage</li>
<li><code>userharbor-smtp</code> — SMTP email sender</li>
<li><code>userharbor-fastapi</code> — FastAPI integration</li>
</ul>
<p>This keeps the core small while still making the common setup easy to install and use.</p>
<h2 id="quick-example">Quick example</h2>
<p>Here is a longer example using SQLAlchemy storage and SMTP email delivery:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">sqlalchemy</span> <span class="kn">import</span> <span class="n">create_engine</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">sqlalchemy.orm</span> <span class="kn">import</span> <span class="n">sessionmaker</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">userharbor</span> <span class="kn">import</span> <span class="n">UserHarbor</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">userharbor_sqlalchemy</span> <span class="kn">import</span> <span class="n">SQLAlchemyUserStore</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">userharbor_smtp</span> <span class="kn">import</span> <span class="n">SMTPEmailSender</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">engine</span> <span class="o">=</span> <span class="n">create_engine</span><span class="p">(</span><span class="s2">&#34;sqlite:///users.db&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">SessionLocal</span> <span class="o">=</span> <span class="n">sessionmaker</span><span class="p">(</span><span class="n">bind</span><span class="o">=</span><span class="n">engine</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">store</span> <span class="o">=</span> <span class="n">SQLAlchemyUserStore</span><span class="p">(</span><span class="n">SessionLocal</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">store</span><span class="o">.</span><span class="n">metadata</span><span class="o">.</span><span class="n">create_all</span><span class="p">(</span><span class="n">engine</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">email_sender</span> <span class="o">=</span> <span class="n">SMTPEmailSender</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">host</span><span class="o">=</span><span class="s2">&#34;smtp.example.com&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">port</span><span class="o">=</span><span class="mi">587</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">username</span><span class="o">=</span><span class="s2">&#34;smtp-user&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">password</span><span class="o">=</span><span class="s2">&#34;smtp-password&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">from_email</span><span class="o">=</span><span class="s2">&#34;noreply@example.com&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">harbor</span> <span class="o">=</span> <span class="n">UserHarbor</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">secret_key</span><span class="o">=</span><span class="s2">&#34;your-secret-key&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">store</span><span class="o">=</span><span class="n">store</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">email_sender</span><span class="o">=</span><span class="n">email_sender</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Register a user</span>
</span></span><span class="line"><span class="cl"><span class="n">harbor</span><span class="o">.</span><span class="n">register</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">username</span><span class="o">=</span><span class="s2">&#34;jane&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">email</span><span class="o">=</span><span class="s2">&#34;jane@example.com&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">password</span><span class="o">=</span><span class="s2">&#34;StrongPassword123!&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Verify email address</span>
</span></span><span class="line"><span class="cl"><span class="n">harbor</span><span class="o">.</span><span class="n">verify_email</span><span class="p">(</span><span class="s2">&#34;verification-token-from-email&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Login</span>
</span></span><span class="line"><span class="cl"><span class="n">session_token</span> <span class="o">=</span> <span class="n">harbor</span><span class="o">.</span><span class="n">login</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">username</span><span class="o">=</span><span class="s2">&#34;jane&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">password</span><span class="o">=</span><span class="s2">&#34;StrongPassword123!&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Verify session</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="n">harbor</span><span class="o">.</span><span class="n">verify_session</span><span class="p">(</span><span class="n">session_token</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;User is logged in&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Get current user</span>
</span></span><span class="line"><span class="cl"><span class="n">current_user</span> <span class="o">=</span> <span class="n">harbor</span><span class="o">.</span><span class="n">get_current_user</span><span class="p">(</span><span class="n">session_token</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">current_user</span><span class="o">.</span><span class="n">username</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Create roles and permissions</span>
</span></span><span class="line"><span class="cl"><span class="n">harbor</span><span class="o">.</span><span class="n">roles</span><span class="o">.</span><span class="n">create</span><span class="p">(</span><span class="s2">&#34;admin&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">harbor</span><span class="o">.</span><span class="n">permissions</span><span class="o">.</span><span class="n">create</span><span class="p">(</span><span class="s2">&#34;users.delete&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">harbor</span><span class="o">.</span><span class="n">roles</span><span class="o">.</span><span class="n">grant_permission</span><span class="p">(</span><span class="s2">&#34;admin&#34;</span><span class="p">,</span> <span class="s2">&#34;users.delete&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">harbor</span><span class="o">.</span><span class="n">grant_role</span><span class="p">(</span><span class="s2">&#34;jane&#34;</span><span class="p">,</span> <span class="s2">&#34;admin&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Check access</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="n">harbor</span><span class="o">.</span><span class="n">has_permission</span><span class="p">(</span><span class="n">session_token</span><span class="p">,</span> <span class="s2">&#34;users.delete&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;User can delete users&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">current_admin</span> <span class="o">=</span> <span class="n">harbor</span><span class="o">.</span><span class="n">require_role</span><span class="p">(</span><span class="n">session_token</span><span class="p">,</span> <span class="s2">&#34;admin&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">current_admin</span><span class="o">.</span><span class="n">username</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Logout</span>
</span></span><span class="line"><span class="cl"><span class="n">harbor</span><span class="o">.</span><span class="n">logout</span><span class="p">(</span><span class="n">session_token</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Change password</span>
</span></span><span class="line"><span class="cl"><span class="n">session_token</span> <span class="o">=</span> <span class="n">harbor</span><span class="o">.</span><span class="n">login</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">username</span><span class="o">=</span><span class="s2">&#34;jane&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">password</span><span class="o">=</span><span class="s2">&#34;StrongPassword123!&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">harbor</span><span class="o">.</span><span class="n">change_password</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">old_password</span><span class="o">=</span><span class="s2">&#34;StrongPassword123!&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">new_password</span><span class="o">=</span><span class="s2">&#34;EvenStrongerPassword123!&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">session_token</span><span class="o">=</span><span class="n">session_token</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Send password reset email</span>
</span></span><span class="line"><span class="cl"><span class="n">harbor</span><span class="o">.</span><span class="n">send_password_reset</span><span class="p">(</span><span class="s2">&#34;jane@example.com&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Reset password</span>
</span></span><span class="line"><span class="cl"><span class="n">harbor</span><span class="o">.</span><span class="n">reset_password</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">new_password</span><span class="o">=</span><span class="s2">&#34;NewStrongPassword123!&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">reset_token</span><span class="o">=</span><span class="s2">&#34;reset-token-from-email&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Delete account</span>
</span></span><span class="line"><span class="cl"><span class="n">session_token</span> <span class="o">=</span> <span class="n">harbor</span><span class="o">.</span><span class="n">login</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">username</span><span class="o">=</span><span class="s2">&#34;jane&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">password</span><span class="o">=</span><span class="s2">&#34;NewStrongPassword123!&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">harbor</span><span class="o">.</span><span class="n">delete_account</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">password</span><span class="o">=</span><span class="s2">&#34;NewStrongPassword123!&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">session_token</span><span class="o">=</span><span class="n">session_token</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span></code></pre></div><h2 id="full-fastapi-example-with-official-integrations">Full FastAPI example with official integrations</h2>
<p>If you want to try the full setup with FastAPI, SQLAlchemy, and SMTP, install all official integrations:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">pip install <span class="s2">&#34;userharbor[all]&#34;</span>
</span></span></code></pre></div><p>Then create a FastAPI application:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">os</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">fastapi</span> <span class="kn">import</span> <span class="n">FastAPI</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">sqlalchemy</span> <span class="kn">import</span> <span class="n">create_engine</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">sqlalchemy.orm</span> <span class="kn">import</span> <span class="n">sessionmaker</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">userharbor</span> <span class="kn">import</span> <span class="n">UserHarbor</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">userharbor_fastapi</span> <span class="kn">import</span> <span class="n">UserHarborFastAPI</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">userharbor_smtp</span> <span class="kn">import</span> <span class="n">SMTPEmailSender</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">userharbor_sqlalchemy</span> <span class="kn">import</span> <span class="n">SQLAlchemyUserStore</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">engine</span> <span class="o">=</span> <span class="n">create_engine</span><span class="p">(</span><span class="s2">&#34;sqlite:///users.db&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">SessionLocal</span> <span class="o">=</span> <span class="n">sessionmaker</span><span class="p">(</span><span class="n">bind</span><span class="o">=</span><span class="n">engine</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">store</span> <span class="o">=</span> <span class="n">SQLAlchemyUserStore</span><span class="p">(</span><span class="n">SessionLocal</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">store</span><span class="o">.</span><span class="n">metadata</span><span class="o">.</span><span class="n">create_all</span><span class="p">(</span><span class="n">engine</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">email_sender</span> <span class="o">=</span> <span class="n">SMTPEmailSender</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">host</span><span class="o">=</span><span class="n">os</span><span class="o">.</span><span class="n">getenv</span><span class="p">(</span><span class="s2">&#34;HOST&#34;</span><span class="p">,</span> <span class="s2">&#34;smtp.example.com&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="n">port</span><span class="o">=</span><span class="nb">int</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">getenv</span><span class="p">(</span><span class="s2">&#34;PORT&#34;</span><span class="p">,</span> <span class="mi">587</span><span class="p">)),</span>
</span></span><span class="line"><span class="cl">    <span class="n">username</span><span class="o">=</span><span class="n">os</span><span class="o">.</span><span class="n">getenv</span><span class="p">(</span><span class="s2">&#34;USERNAME&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="n">password</span><span class="o">=</span><span class="n">os</span><span class="o">.</span><span class="n">getenv</span><span class="p">(</span><span class="s2">&#34;PASSWORD&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="n">from_email</span><span class="o">=</span><span class="n">os</span><span class="o">.</span><span class="n">getenv</span><span class="p">(</span><span class="s2">&#34;USERNAME&#34;</span><span class="p">,</span> <span class="s2">&#34;&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">harbor</span> <span class="o">=</span> <span class="n">UserHarbor</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">secret_key</span><span class="o">=</span><span class="s2">&#34;your-secret-key&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">store</span><span class="o">=</span><span class="n">store</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">email_sender</span><span class="o">=</span><span class="n">email_sender</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">auth</span> <span class="o">=</span> <span class="n">UserHarborFastAPI</span><span class="p">(</span><span class="n">harbor</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">app</span> <span class="o">=</span> <span class="n">FastAPI</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">app</span><span class="o">.</span><span class="n">include_router</span><span class="p">(</span><span class="n">auth</span><span class="o">.</span><span class="n">router</span><span class="p">,</span> <span class="n">prefix</span><span class="o">=</span><span class="s2">&#34;/auth&#34;</span><span class="p">,</span> <span class="n">tags</span><span class="o">=</span><span class="p">[</span><span class="s2">&#34;auth&#34;</span><span class="p">])</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="kn">import</span> <span class="nn">uvicorn</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">uvicorn</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">app</span><span class="p">,</span> <span class="n">host</span><span class="o">=</span><span class="s2">&#34;0.0.0.0&#34;</span><span class="p">,</span> <span class="n">port</span><span class="o">=</span><span class="mi">8000</span><span class="p">)</span>
</span></span></code></pre></div><h2 id="design-principles">Design principles</h2>
<p>The project is built around a few constraints.</p>
<h3 id="the-core-should-stay-small">The core should stay small</h3>
<p>UserHarbor is not meant to become a full identity platform.</p>
<p>The core focuses on basic account-management flows:</p>
<ul>
<li>registration</li>
<li>login</li>
<li>sessions</li>
<li>email verification</li>
<li>password reset</li>
<li>password change</li>
<li>account deletion</li>
<li>simple role-based access control</li>
</ul>
<p>Anything highly application-specific should stay outside the core.</p>
<h3 id="adapters-should-live-outside-the-core">Adapters should live outside the core</h3>
<p>Database, ORM, email, and framework integrations should be separate packages.</p>
<p>That keeps the core independent and makes it easier for other people to build their own integrations.</p>
<p>For example, someone could build:</p>
<ul>
<li><code>userharbor-redis</code></li>
<li><code>userharbor-mongodb</code></li>
<li><code>userharbor-sendgrid</code></li>
<li><code>userharbor-resend</code></li>
<li><code>userharbor-django</code></li>
<li><code>userharbor-flask</code></li>
</ul>
<p>without changing the main package.</p>
<h3 id="the-api-should-be-boring">The API should be boring</h3>
<p>I am trying to keep the public API explicit and predictable.</p>
<p>No hidden framework magic.
No forced database model.
No dependency on one specific way of building Python applications.</p>
<h2 id="current-status">Current status</h2>
<p>The project is still early.</p>
<p>The basic flows work, but I do not consider the API fully stable yet. It is not something I would call production-ready today.</p>
<p>Right now I am mostly looking for feedback around:</p>
<ul>
<li>the public API</li>
<li>the adapter architecture</li>
<li>the boundary between core and integrations</li>
<li>the SQLAlchemy integration</li>
<li>the FastAPI integration</li>
<li>whether simple RBAC belongs in the core</li>
<li>what a good developer experience for custom adapters should look like</li>
</ul>
<h2 id="links">Links</h2>
<p>Documentation:<br>
<a href="https://userharbor.github.io/userharbor/">https://userharbor.github.io/userharbor/</a></p>
<p>Repository:<br>
<a href="https://github.com/userharbor/userharbor">https://github.com/userharbor/userharbor</a></p>
<p>FastAPI integration:<br>
<a href="https://github.com/userharbor/userharbor-fastapi">https://github.com/userharbor/userharbor-fastapi</a></p>
<p>SQLAlchemy adapter:<br>
<a href="https://github.com/userharbor/userharbor-sqlalchemy">https://github.com/userharbor/userharbor-sqlalchemy</a></p>
<p>SMTP adapter:<br>
<a href="https://github.com/userharbor/userharbor-smtp">https://github.com/userharbor/userharbor-smtp</a></p>
<h2 id="feedback-welcome">Feedback welcome</h2>
<p>I would appreciate any feedback, especially from people who have built user-management flows multiple times in Python projects.</p>
<p>Does this adapter-based approach make sense?</p>
<p>Would you expect simple roles and permissions to be part of the core, or should they live in a separate package?</p>
<p>And if you were integrating this into your own project, what would you want the API to look like?</p>
<p><em>Update: I later wrote <a href="/posts/building-userharbor-framework-agnostic-user-management-for-python/">a deeper architectural follow-up about UserHarbor&rsquo;s adapters and executable storage contract</a>.</em></p>
<p><em>Originally published on <a href="https://dev.to/spaceshaman/i-built-userharbor-a-framework-agnostic-user-management-library-for-python-1mkj">DEV Community</a>.</em></p>
]]></content:encoded>
    </item>
    <item>
      <title>About</title>
      <link>https://spaceshaman.github.io/about/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>https://spaceshaman.github.io/about/</guid>
      <description>&lt;p&gt;I build software and open-source tools. I am most interested in the entire development process—from the initial idea and architecture design to a working, useful solution. I work across different areas of software development and choose technologies to fit the problem rather than fitting the problem to a particular stack.&lt;/p&gt;
&lt;p&gt;My projects include libraries, real-time APIs, database tools and automation for everyday development tasks. Whatever the area, I care about clear architecture, simple solutions and tools that solve a specific problem without unnecessary complexity.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I build software and open-source tools. I am most interested in the entire development process—from the initial idea and architecture design to a working, useful solution. I work across different areas of software development and choose technologies to fit the problem rather than fitting the problem to a particular stack.</p>
<p>My projects include libraries, real-time APIs, database tools and automation for everyday development tasks. Whatever the area, I care about clear architecture, simple solutions and tools that solve a specific problem without unnecessary complexity.</p>
<p>In both work and life, I try to follow the principle: <strong>Simple is better than complex.</strong></p>
<h2 id="selected-projects">Selected projects</h2>
<ul>
<li><a href="https://github.com/userharbor/userharbor">UserHarbor</a> — a framework-agnostic user management system for Python applications.</li>
<li><a href="https://github.com/SpaceShaman/socketapi">SocketAPI</a> — a lightweight real-time API framework using one multiplexed WebSocket connection with endpoint-like actions and subscriptions.</li>
<li><a href="https://github.com/SpaceShaman/SQLift">SQLift</a> — a deliberately small command-line migration tool for SQL databases.</li>
<li><a href="https://github.com/SpaceShaman/ORMagic">ORMagic</a> — a simple and lightweight ORM for Python, built on top of Pydantic.</li>
<li><a href="https://github.com/SpaceShaman/autopy.fish">autopy.fish</a> and <a href="https://github.com/SpaceShaman/autoenv.fish">autoenv.fish</a> — Fish plugins that automatically activate Python environments and load variables from <code>.env</code> files.</li>
</ul>
<h2 id="find-me-online">Find me online</h2>
<ul>
<li><a href="https://github.com/SpaceShaman">GitHub</a></li>
<li><a href="https://www.linkedin.com/in/krzysztof-stanislawski/">LinkedIn</a></li>
<li><a href="https://coderlegion.com/user/SpaceShaman">CoderLegion</a></li>
<li><a href="mailto:spaceshaman@tuta.io">spaceshaman@tuta.io</a></li>
</ul>
]]></content:encoded>
    </item>
  </channel>
</rss>
