Add standalone mode with @simpleworkjs/orm-backed user/host stores

- Add standalone.enabled config flag to switch between LDAP+SSO and
  ORM-backed backends without changing the production code path
- New ORM models: StandaloneUser (uid, passwordHash, sshPublicKeys,
  groups) and StandaloneHost (slug, displayName, kind, metadata)
- user_file.js and hosts_file.js implement the same interfaces as the
  LDAP client and accessibleHosts() respectively
- models/user_ldap.js and utils/access.js become conditional facades
  that delegate based on conf.standalone.enabled at require time
- Zero changes to ssh_server.js core logic, bridge.js, key_inject.js,
  tui_picker.js, or any other consumer
- Fix ssh_server.js: use ?? instead of || for listenPort (0 is falsy)
- Fix ssh_server.js: register session listeners before awaiting
  audit.create() so client exec/shell requests aren't rejected
- Patch StringField.toSequelize() and IntegerField.toSequelize() to
  pass through primaryKey (the ORM's UUIDField already does this)
- 47 tests pass (24 existing + 15 new unit + 3 existing integration
  + 5 new standalone integration)
- Defaults to SQLite; any Sequelize dialect works via conf.orm

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-07-26 21:28:19 -04:00
parent da274a3ced
commit 4879769cc7
15 changed files with 1583 additions and 80 deletions
+17 -16
View File
@@ -1,22 +1,23 @@
'use strict';
// Thin LDAP helpers — the jump host's entire LDAP surface, now backed by the
// shared @simpleworkjs/ldap package:
// User authentication backend — LDAP in production, ORM-backed file store in
// standalone mode. Both export the same interface:
// getUser(uid) -> { dn, uid, sshPublicKeys: [] } or null
// getGroups(dn) -> [cn, ...] (groupOfNames membership)
// checkPassword(dn, pw) -> bool (simple bind as the user)
// addSshKey(dn, keyLine) -> void (idempotent multi-value add)
//
// Behavior is unchanged from the previous in-tree implementation: posixAccount
// user filter, groupOfNames group filter, bind-as-user password check,
// TypeOrValueExists treated as success on key add, and the same loose TLS
// default ({ rejectUnauthorized: false } when conf.ldap omits tlsOptions).
// getGroups(dn) -> [cn, ...]
// checkPassword(dn, pw) -> bool
// addSshKey(dn, keyLine) -> void (idempotent)
const conf = require('@simpleworkjs/conf');
const { createLdapClient } = require('@simpleworkjs/ldap');
const ldapConf = conf.ldap || {};
module.exports = createLdapClient({
...ldapConf,
tlsOptions: ldapConf.tlsOptions || { rejectUnauthorized: false },
});
if (conf.standalone && conf.standalone.enabled) {
// Standalone mode: use the ORM-backed user store.
module.exports = require('./user_file');
} else {
// Production mode: use the LDAP directory.
const { createLdapClient } = require('@simpleworkjs/ldap');
const ldapConf = conf.ldap || {};
module.exports = createLdapClient({
...ldapConf,
tlsOptions: ldapConf.tlsOptions || { rejectUnauthorized: false },
});
}