Authentication & Authorization
How the FaceKom apps authenticate agents/operators (the back-office “OSS” apps) and customers (the customer-facing “CSS” portal apps), and how they authorize via the shared acl (@techteamer/acl) library.
Two app families, two auth models
- OSS apps (vuer_oss, esign_oss) = back-office. Passport.js strategies (local / Active Directory / SAML / WebAuthn / TOTP / token), role-based ACL via
@techteamer/acl+config/roles.json, sessions in Sequelize (connect-session-sequelize).- CSS apps (vuer_css, esign_css) = customer portal/kiosk. No Passport, no ACL. Custom session-based customer auth (
req.session.customer), sessions in Redis (connect-redis), SMS 2FA + ClientGate/KAÜ eID SSO. Heavy auth logic is proxied over RabbitMQ-RPC to the OSS side.
All committed
config/{dev,docker}.jsonvalues (web.session.secret,jwt.secret,jwt.keystore[].k, AD/SMTP creds, SAML certs) are template/dev examples, not live secrets. Real deployments override them. See vuer_oss.
1. Mechanism → file map (quick index)
| Mechanism | Where | Concrete source |
|---|---|---|
| Passport bootstrap, serialize/deserialize | vuer_oss | server/web/WebServerAuth.js:69-80,886-888 |
| Local strategy (bcrypt) | vuer_oss / esign_oss | WebServerAuth.js:953-1079 / esign_oss server/web/WebServerAuth.js:234-302 |
| Active Directory (LDAP) | vuer_oss / esign_oss | WebServerAuth.js:1081-1186 |
| SAML — agent SSO | vuer_oss | WebServerAuth.js:285-368,460-508 (@node-saml/passport-saml) |
| SAML — customer eID (ClientGate/KAÜ) | vuer_oss + vuer_css | server/service/ClientGateService.js (samlify); vuer_css server/web/WebServer.js:288-346, routes/client-gate-login.endpoint.js |
| FIDO2 / WebAuthn | vuer_oss | WebServerAuth.js:370-451,510-694 (passport-fido2-webauthn) |
| TOTP / 2FA (authenticator) | vuer_oss | WebServerAuth.js:453-458,696-731; server/service/PasswordService.js:329-387 |
| 2FA via SMS OTP | vuer_oss | PasswordService.js:217-281 |
| JWE token strategy (login) | vuer_oss | WebServerAuth.js:219-250; server/service/JWETokenStrategyService.js |
| JWT token strategy (login) | vuer_oss | WebServerAuth.js:252-283; server/service/JWTTokenStrategyService.js |
| OTL (one-time login) token | vuer_oss / esign_oss | WebServerAuth.js:122-217; server/service/OneTimeLoginService.js; db/model/oneTimeLogin.js |
| JWT/JWE crypto primitives | all | server/service/TokenService.js (node-jose, A256GCM) |
| RAB (resource-access-bypass) token | vuer_oss | server/service/RabService.js; WebServer.js:355-383 |
| Socket.IO auth token | vuer_oss | server/SocketTokenStorage.js; server/socket/events/auth.js |
| Session store (OSS) | vuer_oss / esign_oss | WebServer.js:266-303 (Sequelize); db/model/session.js; server/web/session.js |
| Session store (CSS) | vuer_css / esign_css | vuer_css WebServer.js:275-389 / esign_css web-server.js:100-129 (Redis) |
| ACL evaluation | all OSS | @techteamer/acl → server/acl.js, server/auth.js, config/roles.json |
| ACL library impl | acl repo | src/ACLService.js, src/ACLManager.js, index.js |
2. OSS Passport pipeline (vuer_oss)
Built in server.js:613-655 (StartWebServer). Middleware order is load-bearing:
WebServer: setupLocale → setupResourceAccessBypass → setupSession → setupFlash
WebServerAuth: setupInteractiveAuthStrategy → setupTokenStrategies → start → setupInteractiveRouting → setupOTLTokenRouting
WebServer: setupTwig → setupRequestLogger → setupSessionACLHandler → setupRoutes → … → setupErrorHandler
(verbatim chain at server.js:616-639)
WebServerAuth (server/web/WebServerAuth.js) is required at server.js:13 and constructed with webServer.app (server.js:622).
2.1 Passport core (WebServerAuth.js:62-102)
passport.serializeUserstores onlyuser.id(:69-71);deserializeUserdoesdbModels.User.findByPk(id)(:72-79).start()(:886-951) mountspassport.initialize()+passport.session(), then installs the firewall and the role-resolution middleware (see §6).package.jsondeps:passport@^0.7.0,passport-local@^1.0.0,passport-activedirectory(GitHub tarballTechTeamer/passport-activedirectory),@node-saml/passport-saml@^5.1.0,passport-fido2-webauthn@^0.1.0,passport-totp@^0.0.2,passport-strategy@^1.0.0.
2.2 Strategy selection (setupInteractiveAuthStrategy, :82-102)
Interactive (one of): activeDirectory → AD; else saml → SAML; else (unless disableLocalStrategy) → Local. Additively layered: if security.webAuthn set → WebAuthn; if security.totp set → TOTP. Each pushed strategy is passport.use(strategy.name, strategy).
Token (setupTokenStrategies, :104-120): if jweTokenStrategy.tokenKeyId → JWE token strategy; else if jwtTokenStrategy.tokenKey → JWT token strategy. Always adds the OTL token strategy (named oneTimeLoginToken).
2.3 Local strategy (_setupLocalStrategy, :953-1079)
passport-local LocalStrategy({ passReqToCallback: true }):
- Looks up
User.unscoped().findOne({ where: { username } }). - Password compare via
passwordService.compare(pass, user.password)(bcrypt, see §8). On unknown user it still runs a dummy bcrypt compare against a hard-coded hash to equalize timing (:982). - Rejects with generic
{ message: 'unknown_user' }for: bad password, norights,!isEnabled, nogetMainRole(). Emitsuser.auth.*audit events throughout. - Branching post-password (the multi-factor gate,
:1031-1071):passwordExpiry < now→{ message: 'expired_password' }security.twoFactorAuth.via→{ message: 'two_factor_auth' }- else
security.webAuthn→{ message: 'need_web_authn' } - else
security.totp→{ message: 'need_totp' } - else success →
user.loginevent,ipFilter.releaseIp.
- The route handler
_setupLocalRouting(:733-832) regenerates the session (req.session.regenerate,:739), runspassport.authenticate(strategyNames, …)over all non-webauthn/non-totpstrategies, and maps those messages to redirects (/password-change/<token>,/two-factor-auth/<token>, the WebAuthn URL,/totp).
2.4 Active Directory (_setupActiveDirectoryStrategy, :1081-1186)
- One
passport-activedirectorystrategy perconfig.activeDirectory.servers[], namedLDAP-SERVER-1..n(:1146-1158). - Group→rights mapping via
config.activeDirectory.groups[groupDN] → role(s)(_getRights,:1162-1186). Groups come from a live LDAPgetGroupMembershipForUserquery, or from the user’smemberOfattribute whenactiveDirectory.useMemberOfPropertyis true (:1139-1143). features.roleSwitchoff ⇒ user truncated to a single right (:1106-1108).- Hooks:
ad:userModificationbefore save (:1121). Then sameneed_web_authn/need_totplayering (:1125-1131). - Config schema:
docs/config/schemas/activeDirectory.schema.json(servers[].ldap.{url,baseDN,filter,username,password,attributes},groups,useMemberOfProperty). Schema doc link:docs/features/active-directory/developer-hu.md.
2.5 SAML — agent SSO (_setupSAMLStrategy, :285-368)
- Library:
@node-saml/passport-samlStrategy(:6). - Strategy named
saml, callback path/login/callback,protocol: 'https://',host: config.hosts.oss(:294-302). Extra IdP options merged from thesaml:configoverride hook (:286). - Verify maps
profile.nameID→username/email; rights come from thesaml:profilehook (callHooks('saml:profile', …),:342). Empty rights ⇒user.auth.not_authorized+ reject (:345-348). User upserted via_findOrCreateUserkeyed onemail(case-sensitive) (:337-350). - Routing (
_setupSAMLRouting,:460-508):GET /samlinitiates (encodesRelayStatefrom aredirectTocookie);POST /login/callbackconsumes (validatesRelayStateis a local/path before redirecting tohttps://hosts.oss…). - Cookie
sameSiteforced to'Lax'when SAML is on (WebServer.js:296). - Config schema:
docs/config/schemas/saml.schema.json(entryPoint,issuer,callbackUrl,idpCert,authnContext,identifierFormat,wantAuthnResponseSigned,audience).
2.6 FIDO2 / WebAuthn (:370-451, :510-694)
- Library:
passport-fido2-webauthn(WebAuthnStrategy+SessionChallengeStore,:8,10). Store instancethis.webAuthnStore = new SessionChallengeStore()(:371) — challenges live in the session. - Verify (login): finds
WebAuthnCredential.findOne({ credentialId: id })→ itsUserbyuserId; compares the supplieduserHandleto the storedhandle(base64url) (:389); returns(user, credential.publicKey, { status: 'login' }). - Register: finds user by
userInfo.name; enforcessecurity.webAuthn.maxCredentialsPerUser(default 100, deletes oldest first,:409-434); creates aWebAuthnCredentialrow{ credentialId, handle, publicKey, userId }(:436-441); emitswebAuthnCredential.create/.delete. - Routing (
_setupWebAuthnRouting,:510-694):POST /web-authn/registration/challenge— requires either an authenticated user withusers.change.webAuthnCredentialsACL right, orsecurity.webAuthn.enableSelfRegistration+ a valid token viapasswordService.validateWebAuthnToken(:514-535).POST /web-authn/login/challenge— validates a WebAuthn JWT, returns challenge +allowCredentialIds.POST /web-authn/login—passport.authenticate('webauthn', …); onstatus==='login'callsreq.logInand emitsuser.login {mode:'webAuthn'}; onstatus==='register'returnsregistered/ok.
- DB model
db/model/webAuthnCredential.js:credentialId(unique),handle(nullable),publicKey(TEXT), FKuserId. Manual delete API:server/web/api/common/webauthn-credential-delete.js. - Config:
security.webAuthn.{tokenExpiry='3m', maxCredentialsPerUser=100, enableSelfRegistration, pubKeyCredParams, authenticatorSelection, transports}(security.schema.json:290-312; sample block atWebServerAuth.js:33-60).
2.7 TOTP (:453-458, :696-731) and SMS-2FA (PasswordService.js:217-281)
TOTP (authenticator-app) — passport-totp:
- Strategy returns
(user.key, user.period)(:453-458). - Secret generation:
PasswordService.generateTotpUrlcreates a 40-char key (contactValidation.generateToken(40)), persists aTotpKeyrow, and builds anotpauth://totp/<label>:<username>?secret=<base32>&period=<period>&issuer=<issuer>URI (PasswordService.js:329-387). base32 viathirty-two. - Verify route
POST /totp/verify-otp(:696-731): loadsUser.findByPk(req.session.totpUserId), setsreq.user.key/periodfrom theTotpKey, runspassport.authenticate('totp', { failureRedirect: '/totp/failure' }), thenpasswordService.verifyTotpKey(marksTotpKey.verified=true) and emitsuser.login {mode:'totp'}. - DB model
db/model/totpKey.js:key(STRING),verified(BOOL default false), FKuserId. Configsecurity.totp.{period=30, label='Facekom', issuer='Facekom'}(PasswordService.js:381-386).
SMS OTP 2FA (separate from authenticator TOTP, gated by security.twoFactorAuth.via):
PasswordService.twoFactorAuth(user)(:217-231) issues a JWT-in-JWE token (createTwoFactorToken,tokenExpirydefault'5m',:233-238) → URL/two-factor-auth/<token>, and sends an OTP.sendTwoFactorOTP(:254-281) generates anotpLength(default 6) token and sends via SMS (via: 'sms'; other values throw). The OTP is stashed inreq.session.otpby the login route (WebServerAuth.js:792-796).- Config schema
security.twoFactorAuth.{otpLength, tokenExpiry, via}(security.schema.json:276-288).
2.8 Token login strategies (JWE / JWT / OTL)
All three use the custom UniqueTokenStrategy (server/util/UniqueTokenStrategy.ts) — a passport-strategy subclass that pulls a token from body/query/params/cookie/header (:80-106). Named strategies: JWEToken, JWTToken, oneTimeLoginToken.
- JWE token (
_setupJWETokenStrategy,:219-250+JWETokenStrategyService.js): decrypts the token withtokenService.decrypt(token, jweTokenStrategy.tokenKeyId), runs thetokenStrategy:createUserParamshook, validates{username,rights,firstName,lastName,email?}(regex-checked username/email, non-empty string-array rights), then_findOrCreateUserand updates name/email/rights. Disabled user ⇒user.auth.disabled. Config:jweTokenStrategy.tokenKeyId(docs/config/schemas/jweTokenStrategy.schema.json). - JWT token (
_setupJWTTokenStrategy,:252-283+JWTTokenStrategyService.js):jwt.verify(token, tokenKey)usingjsonwebtoken;jwtTokenStrategy.tokenKeymay be a single key or an array (tries each,:14-18). Same param validation + upsert as JWE. Config:jwtTokenStrategy.tokenKey. - OTL (
_setupOTLTokenStrategy,:122-155): gated byconfig.otl. Consumes a one-time token viaoneTimeLogin.useLoginToken(token); can setreq.session.role/req.session.rulesfrom the token’srequiredRole/meta.sessionRules. RouteGET /otl/:token(setupOTLTokenRouting,:157-217) signs out any current session first, throttles failures viaipFilter.
2.9 OTL token model & service
server/service/OneTimeLoginService.js:createLoginToken(userId, redirectTo, comment, expireDurationSec=300, requiredRole, meta)— token =crypto.randomUUID(), retries up to 10× on unique-constraint collision (:26-45).useLoginTokenenforces single-use (usedAt) + expiry (expiredAt) (:53-74).deleteExpiredpurges rows >1 week old (:81-92).- Model
db/model/oneTimeLogin.js:token(unique),redirectTo,comment,usedAt,expiredAt(required),requiredRole(validated againstUser.allRights),meta(JSON). - CLI:
bin/create_rab_token.jsdocumented in vuer_oss usesTokenService+config.jwt.secret.
3. esign_oss Passport (the lighter back-office)
esign_oss/server/web/WebServerAuth.js — subset of vuer_oss:
- Deps (
esign_oss/package.json):passport@^0.7.0,passport-local@^1.0.0,passport-activedirectory@^1.0.4,passport-unique-token@^3.0.0,@techteamer/acl@^2.0.0,bcryptjs,connect-session-sequelize@^8.0.4. No SAML, WebAuthn, TOTP, or samlify deps. - Strategies:
setupInteractiveAuthStrategychooses AD or Local (:51-59);setupOTLTokenStrategyadds OTL using the npmpassport-unique-tokenStrategy(:4,65) rather than vuer_oss’s custom.tsone. OTL routingGET /otl/:tokenmirrors vuer_oss (:91+). - ACL roles:
config/roles.json=['admin','operator'](vs vuer_oss’s['admin','supervisor','operator','bb_operator']). - Session store: Sequelize (
server/web/web-server.js:3,177,197).
4. Customer auth — CSS portals (vuer_css / esign_css)
No Passport, no
@techteamer/aclhere. Customer identity lives inreq.session.customer; the real credential/2FA logic is RPC'd to the OSS side over RabbitMQ.
4.1 esign_css
- Customer API routes (
server/web/routes.js:232-253) guarded byresolveSession()+requireSession()middleware:/api/customer/{check-username,check-password,create,get-data,login,logout,2f-auth,agreement-check}.2f-authadditionally hascsrfProtection+auth2factorApiIpFilter(routes.js:247). CustomerService(server/service/CustomerService.js, 61 lines) is a thin RPC proxy:loginCustomer/checkPassword/setCredentials/auth2factor/getCustomerData/logoutall forward toserviceContainer.rpcClient.customer.*(esign:rpc-customer, wiredserver.js:105).auth2factor(session, signatureId, username, password, smsToken, verificationAttemptCount)(:32-33) ⇒ SMS-token 2FA implemented in esign_oss.- Session identity check example:
server/web/api/sentry.js:5-16readsreq.session.customer.id.
4.2 vuer_css
rpc-jwt-authRPC client (server.js:118,JwtAuthRPCClient) authenticates the customer/portal JWT against the OSSrpc-jwt-authserver.TokenService(server/service/TokenService.js, identical API to OSS —node-jose, A256GCM JWE) loaded at boot (server.js:259,271).- Device pairing JWTs in
server/socket/events/mobile.js:jwt.sign({ customerId, sessionId }, config.jwt.secret, { expiresIn: config.jwt.expiryPeriod })(:109,156) andjwt.verify(:141). Janus tokens signed inroutes/lobby.endpoint.js:25,system-check.endpoint.js:17. - Socket auth token
server/SocketTokenStorage.js:jwt.sign({ sessionId }, jwt.secret, { expiresIn: '2m' })/verify.
5. SAML / IdP details
Two independent SAML stacks in the codebase:
| Agent SSO | Customer eID (ClientGate / KAÜ / Ügyfélkapu) | |
|---|---|---|
| Library | @node-saml/passport-saml@^5.1.0 | samlify@^2.7.1 + @authenio/samlify-node-xmllint@^2.0.0 |
| Code | vuer_oss WebServerAuth.js:285-368,460-508 | vuer_oss server/service/ClientGateService.js; server/queue/rpc_server/ClientGate.js; vuer_css WebServer.js:288-346, routes/client-gate-login.endpoint.js, routes/saml-metadata.endpoint.js, routes/self-service-client-gate.endpoint.js |
| Config key | saml.* | clientGate.* |
| Who | back-office operators | end customers (self-service identification) |
ClientGateService (ClientGateService.js):
init()registers the samlify schema validator and readscustomization/assets/saml/AuthnRequestTemplate.xml(:35-39).generateAuthnRequestbuilds a custom AuthnRequest XML (samlify can’t add the AttributeStatement extension) withurn:eksz.gov.huattributes (requestData.attributes, e.g.muveletkod=L001,4t) andrequestData.audience(e.g.urn:eksz.gov.hu:1.0:azonositas:kau:2) (:46-77,203-226).handleAuthnResponsecallsserviceProvider.parseLoginResponse(idp, 'post', { body: { SAMLResponse } })(:79-90).- Security posture:
wantAuthnRequestsSigned,isAssertionEncrypted,wantMessageSigned,wantAssertionsSigned,authnRequestsSigned,messageSigningOrderdefault ETS (encrypt-then-sign) (:107-179). The SP is re-created per request specifically to avoid keeping the unencrypted private key resident (:127-136). Known validation errors handled:ERR_FAILED_TO_VERIFY_SIGNATURE,ERR_POTENTIAL_WRAPPING_ATTACK, cert-mismatch (:228-240). - vuer_css redis hand-off (
WebServer.js:288-346): because a cross-origin POST from the IdP carries no session cookie, theSAMLResponse+RelayStateare cached in Redis undersamlresponse:<uuid>withEX 60, then the browser is GET-redirected to/saml/sso/clientGateLogin?samlResponseCacheId=…to re-attach the session before processing.
6. Firewall, session-ACL, RAB (request gating in OSS)
WebServerAuth.start() (:886-951) installs, in order:
passport.initialize()+passport.session()(:887-888).- Firewall bypass rules (
addFirewallBypassRule,:882-884; matched byisRequestBypassable,:865-880): always/login,^/password-change/.+; conditionally^/two-factor-auth/.+,^/otl/.+,/saml+/login/callback, the/web-authn*set,/totp+/totp/verify-otp; plus a functional rule allowing the MNB MSZFH API host’s/external*paths (:914-917). - Firewall (
:920-936): ifreq.rabDatapresent → pass (RAB already authorized); else if!req.userand not bypassable → set aredirectTocookie andres.redirect('/login'). - Role resolution (
:938-948): defaultreq.session.role = req.user.getMainRole();req.rabData.roleoverrides.
Session-based ACL restriction — WebServer.setupSessionACLHandler (WebServer.js:496-516): if the session carries role+rules, builds a transient ACLService from { [role]: rules } and rejects the request with 403 “Forbidden by session” unless isAllowed(req.path.replace(/\//g,'.'), role). This lets OTL/socket sessions be scoped to a subset of a role’s rights.
RAB (Resource Access Bypass) — WebServer.setupResourceAccessBypass (WebServer.js:355-383) + server/service/RabService.js:
createRabToken(accessPath, data, expiresIn='1h'):jwt.sign({ urls, data }, config.jwt.secret)thentokenService.encrypt(...)(JWE-wrapped JWT).verifyRabToken(requestPath, token): JWE-decrypt →jwt.verify→ path must match one ofurls(supports trailing-*prefix match). Token read from?rab=or therabcookie. On mismatch ⇒ HTTP 401.
7. Socket.IO authentication (vuer_oss)
server/SocketTokenStorage.js:createToken(userId, role, rules=null)=jwt.sign({ userId, role, rules }, config.jwt.secret, { expiresIn: '2m' });verifyTokenreturns{userId, role, rules}orundefined.server/socket/events/auth.js(auth:authhandler): verifies the token, loadsUser.findByPk(userData.userId), blocks non-admins during “disaster” mode (:23-27), setsclient.user/role/rules. Builds a per-socket session ACL fromclient.rulesand registers only the event modules the role (and session ACL) allow viaaccessControlList.isAllowed(moduleRule, role)(:40-51). Emitssocket:auth.server/auth.jsis the request-side helper:isAuthorized(client)(user is an enabledUsermodel),hasRight(client, right)(global ACL AND session-ACLclient.rules), plusdoIfAuthorized/doIfHasRight/doIfHasRoomwrappers thatreload()the user before invoking the callback.SocketSessionService.jscorrelates sockets to the Sequelize session via the session cookie (getUserIdFromSessionId,getSessionTokenFromCookie).
8. Passwords (bcrypt) & recovery (vuer_oss)
server/service/PasswordService.js:
hash/compareusebcryptjs(@^3.0.2), costsecurity.password.bcryptCost, optionally offloaded to a worker thread (workers/bcrypt,engines/worker/Runner) (:11-31).- Auth-mode predicates:
isInternalAuthUsed(local),isRemoteAuthUsed(AD/SAML withdisableLocalStrategy),isTokenAuthUsed(JWE/JWT strategy) (:33-43). - Password lifecycle:
changewritesPasswordHistory, enforcespasswordHistorydepth andpasswordExpiry(:77-121);isChangeAvailablethrottles bymaxChangeFrequency/changeCoolDownPeriod(:45-75);resetPasswordissues a temp password emailed/SMSed (:123-173). - Recovery:
createRecoveryToken=jwt.sign({userId}, jwt.secret, {expiresIn})thentokenService.encrypt(...)→ URL/password-change/<token>(:192-201);validateRecoveryTokendecrypts + verifies (:203-215). WebAuthn login token (getWebAuthnLoginUrl/validateWebAuthnToken,:287-321) uses the same JWE-wrapped-JWT pattern. - Config schema
security.password.*(security.schema.json:124-174):bcryptCost,defaultExpiry,passwordHistory,recoveryEmailExpiry,wrongPasswordLimit,wrongPasswordGracePeriod,wrongTwoFactorLimit, etc.
9. JWT / JWE crypto (TokenService) — all apps
server/service/TokenService.js (same shape in vuer_oss, vuer_css, esign_css):
loadKeyStore()builds anode-josekeystore fromconfig.jwt.keystore+config.jwt.customKeystore(:9-15).encrypt(o, keyId='facekom')→ JWE compact,contentAlg: 'A256GCM'(:17-24).decryptreverses (:26-30).sign/verify→ JWS compact with the same keystore (:32-41).- Default key id is
'facekom'. Example dev keystore (vuer_oss config/dev.json:300-307):jwt.secretis a UUID;jwt.keystore= twooct(symmetric) keyskid:'facekom'andkid:'facekomPont'.jwtschema (docs/config/schemas/jwt.schema.json):secret,expiryPeriod,emailExpiryPeriod,deviceChangeExpiryPeriod,keystore/customKeystore(each{use:'sig'|'enc', kty, kid, k}). - Note the two JWT mechanisms in OSS: raw
jsonwebtoken.sign/verifykeyed on the stringconfig.jwt.secret(RAB, OTL recovery, socket, 2FA, device tokens), vs.node-joseJWS/JWE keyed on the keystore (TokenService). They are layered: recovery/RAB/2FA tokens are ajwt.sign(...)string that is thentokenService.encrypt(...)-wrapped in JWE.
10. Session stores
| App | Store | Source |
|---|---|---|
| vuer_oss | Sequelize (connect-session-sequelize@^8), table Session | WebServer.js:266-303; db/model/session.js; server/web/session.js |
| esign_oss | Sequelize (connect-session-sequelize@^8) | server/web/web-server.js:3,177,197 |
| vuer_css | Redis (connect-redis@^9, redis@^5) + separate kiosk session | WebServer.js:2-4,275-389 |
| esign_css | Redis (connect-redis@^9, redis@^5) | server/web/web-server.js:14,24,100-129 |
OSS Sequelize session specifics (WebServer.js:266-303):
SequelizeStore({ db, table:'Session', checkExpirationInterval, expiration }); cookie{ httpOnly:true, secure:true, sameSite: saml?'Lax':security.cookie.sameSite, expires:false };resave:false,saveUninitialized:false,proxy:true.server/web/session.js:listSessions/closeSessionparseSession.dataJSON to find/kill all sessions for auserId;onExpressSessionExpirypatchesclearExpiredSessionsto emituser.auth.session_expired. Onuser.login, all other sessions for that user are destroyed (single-session enforcement,WebServer.js:280-290).
CSS Redis session specifics:
- vuer_css (
WebServer.js:275-389): oneredis.createClient(config.web.session.redis)shared for both the SAML-response cache andRedisStore; per-request switch between normal and kiosk session middleware (req.isKiosk). - esign_css (
web-server.js:100-129): patchessessionStore.generateto dropsameSitetofalsefor iOS-12 Apple-mobile clients (cookie compatibility hack).
The "HA Redis" in vuer_oss (
server/bootstrap/connection/redis.js, gated byHARedis.config) is for shared locks/state, not the session store. vuer_oss sessions are in Postgres via Sequelize. See vuer_oss.
11. ACL model (@techteamer/acl) — shared authorization
Library repo: acl (/Users/levander/coding/facekom-v2-clones/acl). Dependency-free, dual ESM/CJS, @techteamer/acl@2.0.2.
11.1 Rule grammar (src/ACLService.js)
- A role owns two compiled-regex lists:
acceptandreject(:52). import(configJSON)(:10-40): keys are roles; values are rule strings.@-prefixed roles/rules are ignored (used for comments / the@indexmaster list). Duplicate rules within a role are logged as errors.createRule(:61-93): a leading!makes it a reject rule;.is escaped and*compiles to.*; rules become case-insensitive anchored regexes (^…$).- Decision
isAllowed(access, role)(:112-162): validates the access string, then a rule is allowed iff some accept regex matches AND no reject regex matches — i.e. reject always outranks accept. Results are memoized in_resultCache. areAllowed= all-of (AND),anyAllowed= any-of (OR) (:164-229)._logthrowsACLErroronerrorlevel (:238-240) — malformed roles/rules are fatal, not silent.
11.2 ACLManager (src/ACLManager.js)
- Holds an ordered stack
aclList;import(acl)unshifts so the latest-imported ACL wins for a given role (:32-39).getACLForRolereturns the first ACL that defines the role (:50-58).roleListis the flattened unique set across all ACLs (:41-44).
11.3 How OSS wires it
server/acl.js: a singletonACLServicethat readsconfig/roles.jsonat module load andimports it (:5-23). Registered asserviceContainer.service.accessControlList(vuer_oss server.js:133,esign_oss server.js:131), with a logger that suppressesdebugand forwards the rest.config/roles.json(vuer_oss): roles@index(the master catalogue of every rule string, alphabetical, 148 rule strings — 157 total array entries including 9@-comment header lines; the whole role is@-prefixed so it’s import-ignored), then real rolesadmin(129 rules),supervisor(54),operator(31),bb_operator(22). esign_oss:admin,operator.User.allRights(the legal rights set) =acl.roleList(db/model/user.js:250-254);User.prototype.hasRight/getMainRole(:183-195) drive role selection. Theuser.rightscolumn is JSON-encoded and validated againstallRightson write (:36-66).- Request-time checks:
server/auth.js:hasRightcombines the global ACL with the per-session ACL built fromclient.rules; socket modules andsetupSessionACLHandleruse the same primitive (see §6, §7).
Master-list drift risk:
config/roles.json's@indexblock is manually maintained ("IF YOU ADD A NEW RULE ADD IT TO THIS LIST TOO",roles.json:1-11). Because@-rules are import-ignored,@indexis documentation only — the effectiveUser.allRightscomes from the real roles' rule sets, so a rule used by a role but absent from@indexstill works. See acl.
12. Cross-cutting auth controls
- IP throttling / brute-force:
security.ipFilter.*(uncheckedIps,suppressAfterAttempts,throttlePeriodMs,coolDownPeriodMs,security.schema.json:107-122).ipFilter.monitorIp/isIpFiltered/releaseIpinvoked on every failed/successful auth across local/AD/OTL/WebAuthn (e.g.WebServerAuth.js:959-963,177-183,645). Emitsuser.auth.throttled. - Audit events: a consistent
user.auth.*/user.login/user.logoutevent vocabulary (unknown_user,wrong_password,not_authorized,disabled,no_main_role,expired_password,password_success,throttled,session_expired,session_destroy) feedsserver/auditlog.js. Seeserver/listeners/user-listeners.js. - CSRF:
csurf+security.csrf.cookie.*(security.schema.json:15-33); applied to e.g. esign_css/api/customer/2f-auth(routes.js:247). See vuer_oss. - Sensitive config redaction:
security.sensitiveConfigFieldsenumerates secret paths (web.session.secret,jwt.secret,jwt.keystore.*.k,activeDirectory.servers.*.ldap.password,saml.idp,saml.cert,clientGate.*.certContent/keyContent, …) for masking (security.schema.json:175-274). - Helmet / CSP / host-header allow-list:
WebServer.js:38-46rejects unknownHost; CSP nonce per request (:48-51);security.helmet.*. See architecture-overview / vuer_oss.
Unverified / gaps
- I did not open
esign_oss’s full_setupLocalStrategy/AD bodies beyond lines 234-302/304-368 (confirmed structure + deps, not every branch). The esign_oss SMS-2FA /auth2factorserver-side implementation (theesign:rpc-customerserver) was not read. - vuer_css’s
routes/client-gate-login.endpoint.jsandsaml-metadata.endpoint.jsbodies were not opened (their existence + the Redis hand-off inWebServer.jsare verified; the per-endpoint processing is inferred to call OSSClientGateRPC and was not line-verified). - The OSS
server/queue/rpc_server/ClientGate.jsandrpc-jwt-authserver bodies were not opened (only their names/registration). - Exact
passportruntime versions are the manifest ranges inpackage.json, not the lockedyarn.lockpins. - Whether
disableLocalStrategy/saml/activeDirectory/security.webAuthn/security.totpare enabled in any given deployment depends on partner config (not in these repos).
Sources
Read directly (all under /Users/levander/coding/facekom/ unless noted):
- vuer_oss:
server.js(12-13, 128-139, 612-685),server/auth.js,server/acl.js,server/SocketTokenStorage.js,server/web/WebServerAuth.js(full),server/web/WebServer.js(1-130, 240-519),server/web/session.js,server/socket/events/auth.js,server/service/{JWETokenStrategyService,JWTTokenStrategyService,TokenService,RabService,OneTimeLoginService,PasswordService,ClientGateService,SocketSessionService}.js,server/util/UniqueTokenStrategy.ts,server/db/model/{user,webAuthnCredential,totpKey,session,oneTimeLogin}.js,config/roles.json(head + counts),config/dev.json(300-314),docs/config/schemas/{jwt,saml,jweTokenStrategy,jwtTokenStrategy,security,activeDirectory}.schema.json,package.json(deps). - esign_oss:
server/web/WebServerAuth.js(1-120),server.js(61,105,121,131-132),config/roles.json(keys),package.json;server/web/web-server.js(session refs via grep). - vuer_css:
server/web/WebServer.js(2-4, 275-389),server.js(118, 246-271),server/service/TokenService.js,server/SocketTokenStorage.js,server/socket/events/mobile.js(grep),package.json. - esign_css:
server/web/web-server.js(14,24,100-129),server/web/routes.js(232-253),server/web/api/sentry.js,server/service/CustomerService.js,server.js(105,121,148-186),package.json. - acl (
/Users/levander/coding/facekom-v2-clones/acl):src/ACLService.js,src/ACLManager.js,index.js; prior doc acl for manifest/version. - Orientation only (independently re-verified):
/Users/levander/levandor_obsidian/projects/facekom-v2/repos/{vuer_oss,acl}.md.