Authentication¶
This service implements two coexisting auth schemes — one inherited from the someli-api lineage, one added specifically for admin use. Knowing which is which matters when you read a handler.
Scheme A — encrypted-token header (middlewares/auth.js)¶
This is the scheme used by the admin FE (admin_console_R) for all authenticated requests.
// middlewares/auth.js
const jwt = require("jsonwebtoken");
const revokedTokens = require('../helper/revokeToken');
const { decryptData } = require('../helper/tokenGenerator');
module.exports = (req, res, next) => {
let tokenHeaderKey = process.env.TOKEN_HEADER_KEY; // e.g. "Token"
try {
const token = req.header(tokenHeaderKey);
const accountId = req.get('Accountid') ?? null;
if (revokedTokens.has(token)) {
return res.status(401).send("unauthorized access");
}
const verified = decryptData(token);
if (verified) {
res.userId = verified?.userId;
res.role = verified?.role;
res.defaultPaymentId = verified?.defaultPaymentId;
res.accountId = accountId;
next();
} else {
return res.status(401).send("unauthorized access");
}
} catch (error) {
console.log("error", error);
return res.status(401).send("unauthorized access");
}
};
Notes:
- The token is an AES-encrypted JSON blob, not a JWT.
decryptData()inhelper/tokenGenerator.js(byte-identical tosomeli-api's copy) decrypts viacrypto-jsAES. - The
accountIdcomes from a separate header, not from the token. This means the client picks the active account context per request. A user who is a member of multiple accounts can switch accounts by changing the header without re-logging in. (Admin FE:admin_console_R/src/services/api.tscreateAuthHeaders()) resis mutated, notreq. This is unusual.res.userId,res.role,res.accountId,res.defaultPaymentIdare set; downstream handlers read them fromres. This is the same pattern assomeli-apiand is inherited from the same skeleton.- Token revocation is in-memory only.
revokedTokensis aSetpopulated at logout. Process restart clears it. So a logged-out token becomes valid again on the next deploy. Real revocation needs DB-backed tracking. See security.md. jsonwebtokenis imported but not used by this middleware — vestigial.
Used by:
- routes/auth.js:64 — router.use(auth) at top of file. Every endpoint in routes/auth.js requires it.
- Some handlers in routes/routes.js apply it per-handler.
Scheme B — Bearer JWT (methods.js → ensureToken)¶
// methods.js
const jwt = require('jsonwebtoken');
module.exports.ensureToken = function (req, res, next) {
var bearerHeader = req.headers["authorization"];
if (typeof bearerHeader !== 'undefined') {
const bearer = bearerHeader.split(" ");
const bearerToken = bearer[1];
jwt.verify(bearerToken, conf.JWT_SECRET_KEY, (err, result) => {
if (err) res.sendStatus(403);
else next();
});
} else {
res.sendStatus(403);
}
};
This is a plain Authorization: Bearer <jwt> flow, verified against conf.JWT_SECRET_KEY.
Used by:
- routes/routes.js:127 — router.get("/", methods.ensureToken, ...) (health check)
- Possibly other handlers (search the file)
The JWT format and the encrypted-token format are different tokens issued by different endpoints:
| Endpoint | Issues | For |
|---|---|---|
POST /authenticate |
A Bearer JWT (Scheme B) | Older / legacy / native integrations |
POST /webauthenticate |
An encrypted-token (Scheme A) | The admin FE (admin_console_R) |
Both endpoints exist; the FE uses /webauthenticate exclusively (see admin_console_R/src/context/AuthContext.tsx).
Login flow (admin FE perspective)¶
- FE
POST /webauthenticatewith{ email, password }in body +Apptype: <base64>header. - BE verifies via
tMemberSQL, hashes the password withbcryptjs, returns the encrypted token{ token, userId, role, accountId }. - FE stores the token in
localStorageasauth_token. - Each subsequent request:
headers.Token = <encrypted token>; headers.Accountid = <active account>. middlewares/auth.jsdecrypts the token, validates the in-memory revocation set, attaches user context tores, and callsnext().
The token has no expiry encoded inside it (verify by reading decryptData and tracing what tokenGenerator.encryptData puts in the JSON). Sessions are therefore long-lived; logout only revokes the specific token.
Logout flow¶
- FE clears
localStorage.auth_tokenand navigates to/login. - (Optionally) FE calls a logout endpoint that adds the token to
revokedTokensserver-side. - If the process restarts, the in-memory
revokedTokensis empty again. This is a server-side correctness gap — see security.md.
Why this matters¶
Because two schemes coexist:
- Audit risk: a code reviewer who knows only Scheme A might miss a Scheme-B endpoint that was added casually with
ensureTokenand a wrong JWT. - Operational risk: rotating
JWT_SECRET_KEYinvalidates all Scheme-B tokens; rotating the AES key intokenGenerator.jsinvalidates all Scheme-A tokens. Both rotations must happen as deliberate actions; neither is automated. - The role check is done in SQL filters on a per-handler basis, not in middleware. Any new endpoint that forgets to filter by
role_typeis a privilege-escalation bug waiting to happen.
Recommendations¶
- Pick one scheme and migrate. Scheme A is the more powerful (per-account context); deprecate Scheme B.
- Move
revokedTokensto a DB-backed store (tRevokedTokentable withexpiresAt). - Add an expiry to the encrypted token (
{ ..., exp: <ms> }); validate indecryptData. - Add a middleware (
requireRole(...)) that gates endpoints byrole_typedeclaratively. Today the gating is SQL-implicit.