Sunday, 13 July 2026
Full centralized auth service created and deployed to
account.rokojori.com; Roject's local user system replaced
with JWT verification against rokojori-auth.
A complete auth service at c:\rokojori\projects\web-projects\rokojori-auth,
same stack as Roject (Node.js, Express, ts-node, JSON file storage). Built entirely
from scratch this session. Deployed to account.rokojori.com via nginx +
systemd + Let's Encrypt on Server A, port 3001.
Register, login, logout, refresh (body), refresh-session (cookie + redirect),
forgot-password, reset-password, GET /me, PATCH /me/settings, POST /me/password,
DELETE /me. Access token is a 1-hour HS256 JWT containing userId, email, roles,
products, settings. Refresh token is a 30-day UUID stored server-side.
Both are set as HttpOnly cookies on .rokojori.com
and also returned in the response body for non-browser clients.
Three built-in roles. superadmin is bootstrapped via
INITIAL_SUPERADMIN_EMAIL in .env — fires once on
first registration if no superadmin exists. Superadmin can grant/revoke admin
on other users; cannot change their own roles. Admin and superadmin get an
enhanced profile page with a full user list. Role management controls are
superadmin-only.
In-memory per-IP rate limiters on all sensitive endpoints. Login: 10 attempts /
15 min, 3 s delay after attempt 5. Register: 5 / hour. Forgot-password: 20 / 20 min
with escalating delay (5 s → 15 s → 30 s). trust proxy enabled so
real client IPs are seen behind nginx.
Five self-contained pages: login, register, profile, forgot-password, reset-password.
Plain HTML with inline CSS and JS. Login and register accept a ?redirect=
query param. Reset-password fetches the email for the token via
GET /api/auth/reset-token-email and populates a hidden email field
so browsers offer to update the saved password. Profile page includes
change-password (with current password verification) and a two-step delete account
confirmation. Welcome email sent on registration (non-blocking).
GET /api/admin/users (admin + superadmin) lists all users without
password hashes. PATCH /api/admin/users/:id/roles (superadmin only)
sets roles on another user. Guards: cannot change own roles; user
base role is always preserved.
Created workspace/ in the rokojori-auth repo with the same asset
system as the Roject workspace (styles, nav, breadcrumb). A single
workspace/index.html covers features, technical implementation,
data model, JWT payload, deployment, planned work, and a reference to the
Roject workspace for guides and actions.
Originally the refresh token was only returned in the response body. Added a
refresh token cookie so browser clients can silently refresh via
GET /api/auth/refresh-session?redirect=... without re-login.
Non-browser clients continue to use the body token and
POST /api/auth/refresh.
The PATCH /api/admin/users/:id/roles endpoint blocks changes to the calling user's own record to prevent accidental self-demotion or lockout.
SMTP failures on the welcome email are caught and silently ignored so that a misconfigured mail server cannot break user registration.
The response is always { ok: true } regardless of whether the email
exists, preventing account enumeration via timing or response differences.
Deleted routes/auth.ts, sessionStore.ts,
email/, login.html, register.html,
and the login-form and register-form components.
Removed bcryptjs and express-session from
package.json. Removed the User model and
users store from db.ts.
New source/server/middleware/auth.ts reads the
accessToken cookie (or Authorization: Bearer header),
verifies it with the shared JWT_SECRET, and attaches
req.user. Expired tokens on page requests are transparently
redirected to account.rokojori.com/api/auth/refresh-session?redirect=....
API routes return 401 when unauthenticated.
Added cookie-parser and jsonwebtoken.
app-nav.ts now calls GET /api/auth/me on Roject
(which returns req.user from the JWT). On 401 it renders a
"Log in" link pointing to
account.rokojori.com/login.html?redirect=https://roject.rokojori.com.
When authenticated it shows the user's email and a logout link to
account.rokojori.com/api/auth/logout?redirect=....
Added GET /api/auth/logout?redirect=... to rokojori-auth,
mirroring the refresh-session pattern. Clears both cookies,
invalidates the refresh token, and redirects to the given URL.
Roject's systemd service had no environment variables configured, so
JWT_SECRET was silently empty and all JWT verifications failed.
Fixed by adding EnvironmentFile=/opt/roject/.env via
systemctl edit roject followed by a daemon-reload and restart.
Landing screen for unauthenticated users — instead of crashing on dashboard components, show a page that explains the app and offers a login link. Also: graceful 401 handling in data-fetching components (groups, projects, etc.) so they display a sensible message instead of throwing a JS error.