<?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>Web Development on SpaceShaman</title>
    <link>https://spaceshaman.github.io/pl/tags/web-development/</link>
    <description>Recent content in Web Development on SpaceShaman</description>
    <generator>Hugo</generator>
    <language>pl-PL</language>
    <copyright>SpaceShaman</copyright>
    <lastBuildDate>Wed, 01 Jul 2026 17:19:53 +0000</lastBuildDate>
    <atom:link href="https://spaceshaman.github.io/pl/tags/web-development/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Stworzyłem UserHarbor — niezależną od frameworka bibliotekę do zarządzania użytkownikami w Pythonie</title>
      <link>https://spaceshaman.github.io/pl/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/pl/posts/i-built-userharbor-a-framework-agnostic-user-management-library-for-python/</guid>
      <description>Dlaczego stworzyłem UserHarbor i oddzieliłem logikę zarządzania użytkownikami od frameworków webowych, baz danych, ORM-ów i dostawców poczty.</description>
      <content:encoded><![CDATA[<p>Pracując ostatnio nad aplikacją SaaS, po raz kolejny musiałem zaimplementować ten sam zestaw funkcji związanych z kontami użytkowników:</p>
<ul>
<li>rejestrację</li>
<li>logowanie</li>
<li>sesje</li>
<li>weryfikację adresu e-mail</li>
<li>reset hasła</li>
<li>zmianę hasła</li>
<li>usuwanie konta</li>
<li>podstawowe role i uprawnienia</li>
</ul>
<p>Żadna z tych rzeczy nie była szczególnie trudna.</p>
<p>Była za to powtarzalna.</p>
<p>Pisałem już wcześniej podobny kod i nie chciałem w każdym nowym projekcie w Pythonie odtwarzać od podstaw tej samej warstwy zarządzania użytkownikami.</p>
<p>Zacząłem więc pracować nad <strong>UserHarbor</strong>.</p>
<h2 id="czym-jest-userharbor">Czym jest UserHarbor?</h2>
<p><strong>UserHarbor</strong> to niezależna od frameworka biblioteka Pythona służąca do zarządzania kontami użytkowników.</p>
<p>Założenie jest proste:</p>
<blockquote>
<p>rdzeń powinien pozostać mały, przewidywalny i niezależny od konkretnego frameworka webowego, bazy danych, ORM-u czy dostawcy poczty.</p>
</blockquote>
<p>Rdzeń obsługuje logikę zarządzania kontami.
Integracjami zajmują się osobne pakiety adapterów.</p>
<p>Zamiast tworzyć rozwiązanie wyłącznie dla FastAPI, Flaska, Django czy jednego konkretnego stosu technologicznego, chciałem zbudować rdzeń nadający się do użycia w różnych rodzajach aplikacji w Pythonie.</p>
<p>Na przykład w:</p>
<ul>
<li>aplikacjach FastAPI</li>
<li>aplikacjach Flask</li>
<li>aplikacjach Django</li>
<li>narzędziach CLI</li>
<li>narzędziach wewnętrznych</li>
<li>własnych usługach w Pythonie</li>
</ul>
<h2 id="dlaczego-nie-użyć-po-prostu-biblioteki-dla-konkretnego-frameworka">Dlaczego nie użyć po prostu biblioteki dla konkretnego frameworka?</h2>
<p>Istnieją już dobre narzędzia przeznaczone dla konkretnych frameworków.</p>
<p>Mnie jednak zależało na czymś nieco innym.</p>
<p>Nie chciałem, aby logika zarządzania użytkownikami była ściśle powiązana z:</p>
<ul>
<li>frameworkiem webowym</li>
<li>warstwą bazy danych</li>
<li>dostawcą poczty</li>
<li>konkretnym modelem żądań i odpowiedzi</li>
</ul>
<p>Zamiast tego UserHarbor używa niewielkich interfejsów dla takich elementów jak przechowywanie danych i wysyłanie wiadomości e-mail.</p>
<p>Główne interfejsy to:</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>Rdzenia nie interesuje, w jaki sposób użytkownicy są przechowywani ani jak wysyłane są wiadomości e-mail.</p>
<p>Za tę część odpowiadają adaptery.</p>
<h2 id="instalacja">Instalacja</h2>
<p>Jeśli chcesz dostarczyć własne implementacje <code>UserStore</code> i <code>EmailSender</code>, zainstaluj tylko pakiet rdzenia:</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>Aby zainstalować rdzeń wraz z oficjalnymi adapterami SQLAlchemy, SMTP i FastAPI:</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>Możesz też od razu zainstalować wszystkie oficjalne integracje:</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="oficjalne-adaptery">Oficjalne adaptery</h2>
<p>Obecnie dostępnych jest kilka oficjalnych pakietów adapterów:</p>
<ul>
<li><code>userharbor-sqlalchemy</code> — przechowywanie danych za pomocą SQLAlchemy</li>
<li><code>userharbor-smtp</code> — wysyłanie wiadomości e-mail przez SMTP</li>
<li><code>userharbor-fastapi</code> — integracja z FastAPI</li>
</ul>
<p>Dzięki temu rdzeń pozostaje mały, a jednocześnie typową konfigurację można łatwo zainstalować i uruchomić.</p>
<h2 id="szybki-przykład">Szybki przykład</h2>
<p>Oto dłuższy przykład wykorzystujący SQLAlchemy do przechowywania danych i SMTP do wysyłania wiadomości e-mail:</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"># Rejestracja użytkownika</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"># Weryfikacja adresu e-mail</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"># Logowanie</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"># Weryfikacja sesji</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;Użytkownik jest zalogowany&#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"># Pobranie bieżącego użytkownika</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"># Utworzenie ról i uprawnień</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"># Sprawdzenie dostępu</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;Użytkownik może usuwać innych użytkowników&#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"># Wylogowanie</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"># Zmiana hasła</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"># Wysłanie wiadomości umożliwiającej zresetowanie hasła</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 hasła</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"># Usunięcie konta</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="pełny-przykład-fastapi-z-oficjalnymi-integracjami">Pełny przykład FastAPI z oficjalnymi integracjami</h2>
<p>Jeśli chcesz wypróbować pełną konfigurację z FastAPI, SQLAlchemy i SMTP, zainstaluj wszystkie oficjalne integracje:</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>Następnie utwórz aplikację FastAPI:</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="zasady-projektowe">Zasady projektowe</h2>
<p>Projekt opiera się na kilku założeniach.</p>
<h3 id="rdzeń-powinien-pozostać-mały">Rdzeń powinien pozostać mały</h3>
<p>UserHarbor nie ma stać się kompletną platformą do zarządzania tożsamością.</p>
<p>Rdzeń koncentruje się na podstawowych procesach związanych z zarządzaniem kontami:</p>
<ul>
<li>rejestracji</li>
<li>logowaniu</li>
<li>sesjach</li>
<li>weryfikacji adresu e-mail</li>
<li>resetowaniu hasła</li>
<li>zmianie hasła</li>
<li>usuwaniu konta</li>
<li>prostej kontroli dostępu opartej na rolach</li>
</ul>
<p>Wszystko, co jest wysoce specyficzne dla danej aplikacji, powinno pozostać poza rdzeniem.</p>
<h3 id="adaptery-powinny-znajdować-się-poza-rdzeniem">Adaptery powinny znajdować się poza rdzeniem</h3>
<p>Integracje z bazami danych, ORM-ami, pocztą i frameworkami powinny być osobnymi pakietami.</p>
<p>Dzięki temu rdzeń pozostaje niezależny, a inni mogą łatwiej tworzyć własne integracje.</p>
<p>Ktoś mógłby na przykład stworzyć:</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>bez modyfikowania głównego pakietu.</p>
<h3 id="api-powinno-być-nudne">API powinno być nudne</h3>
<p>Staram się, aby publiczne API było jawne i przewidywalne.</p>
<p>Bez ukrytej magii frameworka.
Bez narzuconego modelu bazy danych.
Bez zależności od jednego konkretnego sposobu budowania aplikacji w Pythonie.</p>
<h2 id="obecny-stan">Obecny stan</h2>
<p>Projekt jest nadal na wczesnym etapie rozwoju.</p>
<p>Podstawowe procesy działają, ale API nie jest jeszcze w pełni stabilne. Obecnie nie nazwałbym tej biblioteki gotową do zastosowań produkcyjnych.</p>
<p>W tej chwili zależy mi przede wszystkim na opiniach dotyczących:</p>
<ul>
<li>publicznego API</li>
<li>architektury adapterów</li>
<li>granicy pomiędzy rdzeniem a integracjami</li>
<li>integracji z SQLAlchemy</li>
<li>integracji z FastAPI</li>
<li>tego, czy prosty RBAC powinien należeć do rdzenia</li>
<li>tego, jak powinno wyglądać wygodne tworzenie własnych adapterów</li>
</ul>
<h2 id="linki">Linki</h2>
<p>Dokumentacja:<br>
<a href="https://userharbor.github.io/userharbor/">https://userharbor.github.io/userharbor/</a></p>
<p>Repozytorium:<br>
<a href="https://github.com/userharbor/userharbor">https://github.com/userharbor/userharbor</a></p>
<p>Integracja FastAPI:<br>
<a href="https://github.com/userharbor/userharbor-fastapi">https://github.com/userharbor/userharbor-fastapi</a></p>
<p>Adapter SQLAlchemy:<br>
<a href="https://github.com/userharbor/userharbor-sqlalchemy">https://github.com/userharbor/userharbor-sqlalchemy</a></p>
<p>Adapter SMTP:<br>
<a href="https://github.com/userharbor/userharbor-smtp">https://github.com/userharbor/userharbor-smtp</a></p>
<h2 id="opinie-mile-widziane">Opinie mile widziane</h2>
<p>Będę wdzięczny za wszelkie uwagi, zwłaszcza od osób, które wielokrotnie tworzyły w Pythonie mechanizmy zarządzania użytkownikami.</p>
<p>Czy takie podejście oparte na adapterach ma sens?</p>
<p>Czy spodziewalibyście się prostych ról i uprawnień w rdzeniu, czy raczej w osobnym pakiecie?</p>
<p>A gdybyście integrowali UserHarbor z własnym projektem, jakiego API byście oczekiwali?</p>
<p><em>Aktualizacja: później napisałem <a href="/pl/posts/building-userharbor-framework-agnostic-user-management-for-python/">bardziej szczegółowy artykuł o architekturze adapterów UserHarbor i wykonywalnym kontrakcie warstwy danych</a>.</em></p>
<p><em>Artykuł pierwotnie opublikowany po angielsku na <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>
  </channel>
</rss>
