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 @@ + + +
+ + +Plan — In Progress
+
+ Extracting the user system from Roject into a standalone centralized auth service
+ at account.rokojori.com, shared across all rokojori projects.
+
+ 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.
+
+ A standalone Express service, identical stack to Roject (Node.js, ts-node, + JSON file storage). Owns everything identity-related: +
+
+ 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.
+
source/server/routes/auth.tssource/server/middleware/auth.tssource/server/db.tsbcryptjs, express-sessionsource/pages/login.html, register.htmlsource/server/email/ (moves to rokojori-auth)account.rokojori.com
+ 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.
+
+ 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.
+
+ 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.
+
+ 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.
+
+ 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.
+
.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.
+
roject.rokojori.com, not logged in.account.rokojori.com/login?redirect=https://roject.rokojori.com
+ account.rokojori.com/login.html.rokojori-auth verifies password, issues JWT, sets cookie on
+ .rokojori.com.redirect URL — cookie is already valid there.
+ Logout works the same way: link to
+ account.rokojori.com/logout?redirect=..., which clears the cookie
+ and redirects back.
+
{
+ "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"
+}
+ + 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.
+
+ 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.
+
+ 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. +
+{ "token": "uuid", "userId": "uuid", "expiresAt": "2026-08-12T00:00:00Z" }
+ { "token": "uuid", "userId": "uuid", "expiresAt": "2026-07-12T01:00:00Z" }
+ 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
+ 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
+ JWT_SECRET=... — same value on all services that verify tokens
+
+ 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
+ JWT_SECRET env var to the new app.account.rokojori.com.
+ 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. +
+ +