Turtor Docs
API

Autentizace

Magic Link, Google OAuth, email/password, JWT tokeny v HTTP-only cookies a autorizační guardy

Turtor podporuje tři metody autentizace: Magic Link (OTP přes e-mail), Google OAuth (přes WorkOS) a email/password. JWT tokeny jsou uložené v HTTP-only cookies.

Metody přihlášení

Primární metoda přihlášení pro veřejný web. Odešle 6místný kód na e-mail.

POST /api/v1/auth/magic-link/send    { email }
POST /api/v1/auth/magic-link/verify  { email, code }
ParametrHodnotaPopis
Délka kódu6 číslicNumerický kód
Platnost10 minutDoba platnosti kódu
Max pokusů3Ověřovací pokusy na kód
Rate limit5/hodOdeslání kódu na e-mail
Uložení kódubcryptHashovaný s 10 koly

Pokud e-mail neexistuje, automaticky se vytvoří nový účet s rolí company_admin.

Google OAuth (WorkOS SSO)

GET  /api/v1/auth/login/workos  -> Přesměrování na WorkOS AuthKit
GET  /api/v1/auth/callback      -> Zpracování OAuth callbacku
POST /api/v1/auth/callback      -> API varianta (body: { code })

Nový OAuth uživatel je vytvořen s rolí company_admin. Pokud e-mail již existuje, WorkOS ID se propojí s existujícím účtem.

Email a heslo

POST /api/v1/auth/register  { email, password, firstName, lastName, role }
POST /api/v1/auth/login     { email, password }

Heslo je hashováno přes bcrypt (12 kol). Registrace odesílá ověřovací e-mail.

JWT tokeny

Uživatel se přihlásí (libovolná metoda)


Backend vygeneruje tokeny


Tokeny nastaveny jako HTTP-only cookies

        ├── access_token:  15 min, sameSite: lax
        └── refresh_token: 7 dní, path: /api/v1/auth/refresh

Payload tokenu

interface TokenPayload {
  sub: string;         // user.id
  email: string;
  roles: UserRole[];
  activeRole: UserRole;
}

Obnovení tokenu

POST /api/v1/auth/refresh

Refresh token je omezený na cestu /api/v1/auth/refresh -- frontend jej nikdy nevidí. Klient automaticky obnovuje token každých 12 minut a při návratu na záložku.

API endpointy

MetodaEndpointAutentizaceRate limitPopis
POST/auth/registerVeřejný3/minRegistrace
POST/auth/loginVeřejný5/minPřihlášení
POST/auth/magic-link/sendVeřejný3/minOdeslání OTP
POST/auth/magic-link/verifyVeřejný5/minOvěření OTP
GET/auth/login/workosVeřejný--Google OAuth
POST/auth/callbackVeřejný--OAuth callback
POST/auth/refreshCookie--Obnovení tokenu
POST/auth/logoutVeřejný--Odhlášení
GET/auth/meJWT--Profil uživatele
PATCH/auth/meJWT--Aktualizace profilu
POST/auth/switch-roleJWT--Přepnutí role
POST/auth/forgot-passwordVeřejný3/minReset hesla
POST/auth/reset-passwordVeřejný5/minNové heslo
POST/auth/change-passwordJWT--Změna hesla

Unified profil (GET /auth/me)

Vrací kompletní profil kombinující data uživatele, instruktora, firmy a garanta:

interface UnifiedProfileDto {
  id: string;
  email: string;
  firstName: string;
  lastName: string;
  phone: string | null;
  phoneVerified: boolean;
  roles: UserRole[];
  activeRole: UserRole;
  activeCompanyId: string | null;
  emailVerified: boolean;

  // Vnořené profily (null pokud neexistují)
  instructor: InstructorProfileDto | null;
  company: CompanyProfileDto | null;
  garantRegions: GarantRegionDto[] | null;
  preferences: UserPreferencesDto;
}

Guardy na backendu

// Pouze autentizovaný uživatel
@UseGuards(JwtAuthGuard)

// Autentizovaný + konkrétní role
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles(UserRole.ADMIN)

// Získání aktuálního uživatele
async endpoint(@CurrentUser() user: User) { }

Ochrana routes na frontendu

Admin aplikace

Root layout vynucuje autentizaci přes beforeLoad:

export const Route = createRootRouteWithContext<RouterContext>()({
  beforeLoad: async ({ location, context }) => {
    if (location.pathname.startsWith('/auth')) return;

    const user = await ensureAuth(context);
    if (!user) {
      throw redirect({
        to: '/auth/login',
        search: { returnUrl: location.pathname },
      });
    }

    // Kontrola oprávnění role
    const allowedRoles = getAllowedRoles(location.pathname);
    if (allowedRoles && !hasRoutePermission(user.activeRole, allowedRoles)) {
      throw redirect({ to: '/dashboard' });
    }
  },
});

Web aplikace

Veřejný web nevyžaduje autentizaci. Přihlášení je vyžadováno jen pro checkout a profilové stránky.

Synchronizace mezi záložkami

Web aplikace synchronizuje stav přihlášení mezi záložkami pomocí BroadcastChannel (s localStorage fallbackem):

import { broadcastLogin, broadcastLogout, subscribeToAuthEvents } from '~/lib/auth';

// Po přihlášení
broadcastLogin();

// Po odhlášení
broadcastLogout();

// Ostatní záložky reagují automaticky
subscribeToAuthEvents(onLogout, onLogin);

Uživatelské role

RoleHodnotaPopis
AdministrátoradminPlný přístup k systému
GarantgarantSchvaluje instruktory, spravuje region
InstruktorinstructorVyučuje kurzy
Administrátor firmycompany_adminRezervuje kurzy, spravuje firmu
Zaměstnanec firmycompany_employeeProhlíží firemní rezervace
Obchodní zástupcesales_repSpravuje leady a sales pipeline

Uživatelé mohou mít více rolí a přepínat mezi nimi přes POST /auth/switch-role.