diff --git a/workspace/_assets_/nav-data.js b/workspace/_assets_/nav-data.js index b07f75c..1aa5302 100644 --- a/workspace/_assets_/nav-data.js +++ b/workspace/_assets_/nav-data.js @@ -5,6 +5,9 @@ var NAV_DATA = { { title: 'Outline', path: 'outline/index.html', + children: [ + { title: 'rokojori-auth Restructure', path: 'outline/auth-restructure.html' }, + ] }, { title: 'Guides', diff --git a/workspace/outline/auth-restructure.html b/workspace/outline/auth-restructure.html new file mode 100644 index 0000000..b368121 --- /dev/null +++ b/workspace/outline/auth-restructure.html @@ -0,0 +1,365 @@ + + + + + + rokojori-auth Restructure — Roject + + + + +
+ +
+

Plan — In Progress

+

rokojori-auth

+

+ Extracting the user system from Roject into a standalone centralized auth service + at account.rokojori.com, shared across all rokojori projects. +

+
+ +
+

Why

+ +
+

+ Roject currently has a self-contained user system (registration, login, sessions + via express-session, bcrypt password hashing, JSON file storage). + Any future rokojori project — a personal website, desktop tools, other services — + would need to duplicate this, resulting in multiple disconnected user systems with + no shared identity. +

+

+ The goal is one user record per person across all rokojori projects. Roles, + permissions, purchased products, and global settings all live in one place. + New projects plug in by verifying a JWT — no auth code to write, no user table + to maintain. +

+

+ Payments will be handled by Polar (merchant of record — handles + VAT/tax automatically). Polar is not used as a user store; it only tells us who + paid for what. That purchase record is then written to the user in + rokojori-auth via a webhook and stored there permanently. +

+
+
+ +
+

Two Repos, Clear Responsibilities

+ +
+

rokojori-auth — new repo

+

+ A standalone Express service, identical stack to Roject (Node.js, ts-node, + JSON file storage). Owns everything identity-related: +

+
    +
  • User registration and login
  • +
  • JWT issuance (access token + refresh token)
  • +
  • Password reset via email
  • +
  • User profile management
  • +
  • Roles, permissions, products, global settings
  • +
  • Login and register HTML pages (shared across all apps)
  • +
+
+ c:\rokojori\projects\web-projects\rokojori-auth + account.rokojori.com +
+
+ +
+

roject — existing repo, becomes a client

+

+ Roject drops all user management and becomes a JWT-validating client. + Its own data (groups, projects, files, layouts) continues to be stored + locally — it just references userId from the JWT instead + of a local user table. +

+
    +
  • Remove source/server/routes/auth.ts
  • +
  • Remove source/server/middleware/auth.ts
  • +
  • Remove user parts of source/server/db.ts
  • +
  • Remove bcryptjs, express-session
  • +
  • Remove source/pages/login.html, register.html
  • +
  • Remove source/server/email/ (moves to rokojori-auth)
  • +
  • Add JWT verification middleware (reads cookie, verifies with shared secret)
  • +
  • Login / logout nav links point to account.rokojori.com
  • +
+
+
+ +
+

Two Login Modes

+ +
+

Browser — redirect flow

+

+ Used by web apps (Roject, website). App links to + account.rokojori.com/login?redirect=..., user logs in on the + HTML page, cookie is set on .rokojori.com, browser is redirected + back. No token handling needed in the app. +

+
+ +
+

Non-browser — direct API call

+

+ Used by Electron, mobile apps, Godot, CLI tools, or any client that manages + its own storage. POST credentials directly to the login endpoint and receive + tokens in the response body — no browser, no redirect, no cookie. +

+
POST https://account.rokojori.com/api/auth/login
+{ "email": "...", "password": "..." }
+
+→ { "accessToken": "...", "refreshToken": "..." }
+

+ The client stores the tokens locally (file, memory, secure storage) and sends + the access token on every request as a header: +

+
Authorization: Bearer <accessToken>
+

+ When the access token expires, the client calls + POST /api/auth/refresh with the refresh token to get a new one — + no re-login needed. +

+
+ +
+

Same endpoint, both modes

+

+ POST /api/auth/login always returns JSON with the tokens. + The HTML login page additionally sets the cookie and performs the redirect + client-side after reading the response. Non-browser clients ignore the cookie + and just use the response body. No separate endpoints needed. +

+
+
+ +
+

Token Strategy

+ +
+

Access token — short-lived JWT

+

+ Signed with a shared JWT_SECRET (HS256). Contains everything + an app needs to know about the user — no database call required at runtime: +

+
{
+  "userId": "uuid",
+  "email": "user@example.com",
+  "roles": ["user"],
+  "products": ["roject-pro"],
+  "settings": { "theme": "dark", "language": "en" }
+}
+

+ Lifetime: ~1 hour. Apps verify the signature locally — no round-trip to + rokojori-auth on every request. +

+
+ +
+

Refresh token — long-lived, server-side

+

+ A random UUID stored in refreshTokens.json on + rokojori-auth. Used only to request a new access token when + the current one expires. Lifetime: 30 days. Invalidated on logout. +

+
+ +
+

Cookie on .rokojori.com

+

+ On login, rokojori-auth sets the access token as a cookie + on the .rokojori.com domain. This makes it automatically + available to all subdomains (roject.rokojori.com, + account.rokojori.com, future services) without any extra + token-passing logic. +

+

+ Web apps read the cookie directly. Desktop/CLI apps (e.g. Electron later) + store the token locally and send it in the Authorization header. +

+
+
+ +
+

Login Flow

+ +
+
    +
  1. User visits roject.rokojori.com, not logged in.
  2. +
  3. Clicks Login → browser goes to + account.rokojori.com/login?redirect=https://roject.rokojori.com +
  4. +
  5. User submits credentials on account.rokojori.com/login.html.
  6. +
  7. rokojori-auth verifies password, issues JWT, sets cookie on + .rokojori.com.
  8. +
  9. Redirects to the redirect URL — cookie is already valid there.
  10. +
  11. Roject reads and verifies the cookie — user is logged in.
  12. +
+

+ Logout works the same way: link to + account.rokojori.com/logout?redirect=..., which clears the cookie + and redirects back. +

+
+
+ +
+

User Data Model

+ +
+

users.json — one record per user

+
{
+  "id": "uuid",
+  "email": "user@example.com",
+  "passwordHash": "...",
+  "roles": ["user"],
+  "products": [
+    { "id": "roject-pro", "acquiredAt": "2026-07-12", "source": "polar" }
+  ],
+  "settings": {
+    "theme": "dark",
+    "language": "en"
+  },
+  "createdAt": "2026-07-12T00:00:00Z"
+}
+
+ +
+

Roles and permissions

+

+ Role types are defined in code (a TypeScript object or JSON file) mapping + role names to permission sets. The user record stores only the role name — + the permissions are derived at runtime. +

+
{
+  "admin": ["manage-users", "manage-products", "access-all"],
+  "user":  ["access-own"]
+}
+

+ App-specific roles (e.g. "editor of project X" in Roject) are never stored + in rokojori-auth — they stay in the respective app, keyed by + userId. +

+
+ +
+

Products

+

+ When Polar fires a purchase webhook, a record is appended to the user's + products array. Apps check product IDs from the JWT at runtime — + no Polar dependency needed outside of the webhook handler. +

+
+ +
+

Settings

+

+ A free JSON object for global preferences (theme, language, timezone, etc.) + that should be consistent across all apps. App-specific preferences (Roject + panel layout, editor config) stay in the respective app. +

+
+ +
+

refreshTokens.json

+
{ "token": "uuid", "userId": "uuid", "expiresAt": "2026-08-12T00:00:00Z" }
+
+ +
+

resetTokens.json

+
{ "token": "uuid", "userId": "uuid", "expiresAt": "2026-07-12T01:00:00Z" }
+
+
+ +
+

API Endpoints

+ +
+
POST /api/auth/register          — create user, issue tokens
+POST /api/auth/login             — verify password, issue tokens
+POST /api/auth/logout            — invalidate refresh token, clear cookie
+POST /api/auth/refresh           — swap refresh token for new access token
+POST /api/auth/forgot-password   — send password reset email
+POST /api/auth/reset-password    — consume reset token, set new password
+GET  /api/auth/me                — return user data from access token
+PATCH /api/auth/me/settings      — merge-update global settings
+
+
+ +
+

File Structure — rokojori-auth

+ +
+
rokojori-auth/
+  source/
+    server/
+      routes/
+        auth.ts              — all auth endpoints
+      middleware/
+        requireAuth.ts       — JWT verification middleware
+      email/
+        EmailSender.ts       — interface
+        SMTPEmailSender.ts   — Nodemailer implementation
+        EmailService.ts      — static facade
+      roles.ts               — role → permissions map
+      db.ts                  — user, refresh token, reset token storage
+      index.ts               — Express app entry point
+    pages/
+      login.html             — accepts ?redirect= param
+      register.html          — accepts ?redirect= param
+      profile.html           — view/edit email, change password
+  scripts/
+    copy-pages.js
+  package.json
+  tsconfig.json
+  tsconfig.client.json
+  tsconfig.ts-node.json
+  .gitignore
+  .env                       — never committed
+
+
+ +
+

Environment Variables

+ +
+

Shared across all services

+
JWT_SECRET=...          — same value on all services that verify tokens
+ +

rokojori-auth only

+
SMTP_HOST=smtp.ionos.com
+SMTP_PORT=587
+SMTP_SECURE=false
+SMTP_USER=noreply@rokojori.com
+SMTP_PASS=...
+SMTP_FROM=noreply@rokojori.com
+PORT=3001               — or whichever port nginx proxies to
+
+
+ +
+

Adding a New App Later

+ +
+
    +
  1. Add JWT_SECRET env var to the new app.
  2. +
  3. Add JWT verification middleware (copy from Roject).
  4. +
  5. Point login/logout links at account.rokojori.com.
  6. +
  7. Done — no auth code to write, no user table to create.
  8. +
+
+
+ + + +
+ + + + + diff --git a/workspace/outline/index.html b/workspace/outline/index.html index e42f87e..0030651 100644 --- a/workspace/outline/index.html +++ b/workspace/outline/index.html @@ -230,6 +230,27 @@ +
+

In progress — Centralized auth: rokojori-auth

+

+ The user system currently embedded in Roject is being extracted into a + standalone auth service, rokojori-auth, deployed at + account.rokojori.com. It will handle registration, login, + JWT issuance, password reset, and user data (roles, products, settings) + for all rokojori projects. Roject will become a JWT-validating client + with no user storage of its own. +

+

+ See auth-restructure for the full plan. +

+
+ rokojori-auth + account.rokojori.com + JWT + centralized identity +
+
+

Smaller open improvements