@xeplr/auth 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/bin/migrate.js +58 -25
  2. package/bin/server.js +12 -30
  3. package/index.js +188 -48
  4. package/lib/adminRouter.js +20 -92
  5. package/lib/authHelper.js +26 -3
  6. package/lib/authMiddleware.js +39 -13
  7. package/lib/authRouter.js +144 -12
  8. package/lib/authService.js +448 -16
  9. package/lib/hooks.js +50 -0
  10. package/lib/mtMembershipMiddleware.js +61 -0
  11. package/lib/seed.js +74 -0
  12. package/lib/sessionService.js +54 -6
  13. package/lib/ticketService.js +88 -0
  14. package/lib/tokenDecision.js +45 -0
  15. package/migrations/0001_extensions.sql +9 -0
  16. package/migrations/0002_users.sql +31 -0
  17. package/migrations/0003_catalog_tables.sql +82 -0
  18. package/migrations/0004_role_mappings.sql +78 -0
  19. package/migrations/0005_user_tenants_mapping.sql +39 -0
  20. package/migrations/0006_seed_catalog.sql +120 -0
  21. package/models/Api.js +3 -0
  22. package/models/ApisRolesMapping.js +3 -0
  23. package/models/Menu.js +3 -0
  24. package/models/MenuRolesMapping.js +3 -0
  25. package/models/Role.js +8 -0
  26. package/models/UiElement.js +3 -0
  27. package/models/UiElementsRolesMapping.js +3 -0
  28. package/models/UiPage.js +3 -0
  29. package/models/UiPagesRolesMapping.js +3 -0
  30. package/models/User.js +3 -13
  31. package/models/UserRolesMapping.js +3 -0
  32. package/models/UserTenantsMapping.js +20 -2
  33. package/models/index.js +0 -2
  34. package/package.json +26 -5
  35. package/migrations/0001_users.js +0 -22
  36. package/migrations/0002_menus.js +0 -16
  37. package/migrations/0003_apis.js +0 -16
  38. package/migrations/0004_uiPages.js +0 -16
  39. package/migrations/0005_uiElements.js +0 -16
  40. package/migrations/0006_roles.js +0 -15
  41. package/migrations/0007_apisRolesMapping.js +0 -16
  42. package/migrations/0008_uiPagesRolesMapping.js +0 -16
  43. package/migrations/0009_uiElementsRolesMapping.js +0 -16
  44. package/migrations/0010_menuRolesMapping.js +0 -16
  45. package/migrations/0011_userRolesMapping.js +0 -16
  46. package/migrations/0012_users_add_reset_token.js +0 -13
  47. package/migrations/0013_add_isPublic.js +0 -27
  48. package/migrations/0014_users_add_activation_token.js +0 -13
  49. package/migrations/0015_add_mt_columns.js +0 -41
  50. package/migrations/0016_tenants.js +0 -23
  51. package/migrations/0017_userTenantsMapping.js +0 -20
  52. package/models/Tenant.js +0 -63
  53. package/seeds/001_roles.js +0 -26
  54. package/seeds/002_apis.js +0 -70
  55. package/seeds/003_pages.js +0 -41
  56. package/seeds/004_elements.js +0 -46
  57. package/seeds/005_menus.js +0 -35
  58. package/seeds/006_default_tenant.js +0 -50
  59. package/seeds/zzz_admin_access.js +0 -49
package/bin/migrate.js CHANGED
@@ -1,20 +1,38 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ // Reads process.env ONLY. The consuming app loads its .env (e.g. via dotenv-cli
4
+ // in the npm script) — @xeplr/* packages never read .env files.
5
+
3
6
  const path = require('path');
4
- const { up, rollback, status } = require('@xeplr/db').migrator;
7
+ const { up, status } = require('@xeplr/db').sqlMigrator;
8
+ const { resolveConfig, migrationsFor } = require('@xeplr/db');
5
9
 
6
10
  /**
7
11
  * xeplr-auth-migrate
8
12
  *
9
- * Runs auth migrations bundled with this package.
10
- * Reuses xeplr-db's migrator — no duplicated knex logic.
13
+ * Runs auth's bundled .sql migrations, followed by the consumer app's
14
+ * EXTENSION migrations dir (schema + data auth has no separate seed step;
15
+ * base data lives in migrations so it runs exactly once). Reuses xeplr-db's
16
+ * sqlMigrator — hand-written .sql files, no down(), ledger-tracked.
17
+ *
18
+ * The app points at its extension dir(s) via env: XEPLR_AUTH_MIGRATIONS
19
+ * (or --extDir). Base migrations run first, then the extension dir(s) — one
20
+ * shared ledger, tracked by filename.
21
+ *
22
+ * XEPLR_AUTH_MIGRATIONS accepts a COMMA-SEPARATED list, not just one path —
23
+ * this is how several consuming apps' extension migrations (workflow's,
24
+ * jobs', BI's own) can all land in the SAME target database from a single
25
+ * controlled run, without any one app's migrations folder having to contain
26
+ * another's:
27
+ * XEPLR_AUTH_MIGRATIONS=/path/to/bi/migrations-auth,/path/to/workflow/migrations-auth
28
+ * Sibling apps must keep their migration FILENAMES distinct from each
29
+ * other's — the ledger is keyed by filename alone, not by which directory it
30
+ * came from.
11
31
  *
12
32
  * Usage:
13
- * xeplr-auth-migrate up [--db <database>]
14
- * xeplr-auth-migrate rollback [--db <database>]
15
- * xeplr-auth-migrate status [--db <database>]
33
+ * xeplr-auth-migrate up [--extDir <dir>[,<dir>...]] [--db <database>]
34
+ * xeplr-auth-migrate status [--extDir <dir>[,<dir>...]] [--db <database>]
16
35
  */
17
-
18
36
  function parseArgs(argv) {
19
37
  const args = { _: [] };
20
38
  for (let i = 0; i < argv.length; i++) {
@@ -33,30 +51,37 @@ async function main() {
33
51
  const args = parseArgs(process.argv.slice(2));
34
52
  const command = args._[0];
35
53
 
36
- // Always use bundled migrations directory
54
+ // Bundled auth migrations (base) + the app's extension migrations dir, which
55
+ // the app configures ONCE via XEPLR_AUTH_MIGRATIONS (or --extDir). Base runs
56
+ // first, then the extension — one shared ledger.
37
57
  const options = {
38
58
  ...args,
39
- db: args.db || process.env.DB_AUTH || process.env.DB_NAME,
40
- dir: path.join(__dirname, '..', 'migrations')
59
+ db: args.db || process.env.AUTH_DB_NAME,
60
+ dir: path.join(__dirname, '..', 'migrations'),
61
+ // Same convention as every other entry point and every other library —
62
+ // splitting and existence-checking live in @xeplr/db's
63
+ // resolveDirectories(), not here. This CLI having had its own parseExtDir
64
+ // while boot() had none is exactly how the two diverged.
65
+ extDir: args.extDir || args['ext-dir'] || migrationsFor('auth'),
66
+ connectionName: args['connection-name'] || args.connectionName || 'auth'
41
67
  };
42
68
 
69
+ if (['up', 'status'].indexOf(command) !== -1) {
70
+ // App tells us which secret to use (decoupled from the connection name):
71
+ // --connection <encrypted> | --connection-env <ENV_NAME>. Omit → legacy
72
+ // <connectionName>_CONNECTION fallback inside resolveConfig.
73
+ var connSource = args.connection ||
74
+ (args['connection-env'] && process.env[args['connection-env']]) || undefined;
75
+ await resolveConfig(options.connectionName, connSource);
76
+ }
77
+
43
78
  switch (command) {
44
79
  case 'up': {
45
80
  const result = await up(options);
46
81
  if (result.migrations.length === 0) {
47
82
  console.log('Already up to date');
48
83
  } else {
49
- console.log(`Batch ${result.batch} ran ${result.migrations.length} migrations:`);
50
- result.migrations.forEach(m => console.log(` - ${m}`));
51
- }
52
- break;
53
- }
54
- case 'rollback': {
55
- const result = await rollback(options);
56
- if (result.migrations.length === 0) {
57
- console.log('Nothing to rollback');
58
- } else {
59
- console.log(`Rolled back ${result.migrations.length} migrations:`);
84
+ console.log(`Ran ${result.migrations.length} migrations:`);
60
85
  result.migrations.forEach(m => console.log(` - ${m}`));
61
86
  }
62
87
  break;
@@ -71,21 +96,29 @@ async function main() {
71
96
  } else {
72
97
  console.log('No pending migrations');
73
98
  }
99
+ if (result.drift.length) {
100
+ console.log('WARNING - applied migrations edited after the fact (checksum mismatch):');
101
+ result.drift.forEach(m => console.log(` ! ${m}`));
102
+ }
74
103
  break;
75
104
  }
76
105
  default:
77
106
  console.log('xeplr-auth-migrate - Auth database migrations');
78
107
  console.log('');
79
108
  console.log('Commands:');
80
- console.log(' up [--db <name>] Run pending migrations');
81
- console.log(' rollback [--db <name>] Rollback last batch');
82
- console.log(' status [--db <name>] Show migration status');
109
+ console.log(' up [--extDir <dir>[,<dir>...]] Run base + app extension migrations');
110
+ console.log(' status [--extDir <dir>[,<dir>...]] Show migration status');
111
+ console.log('');
112
+ console.log(' App extension dir(s): --extDir or XEPLR_AUTH_MIGRATIONS env (comma-separated for more than one)');
83
113
  console.log('');
84
114
  console.log('Options:');
85
- console.log(' --db Database name (or DB_AUTH env)');
115
+ console.log(' --db Database name (or AUTH_DB_NAME env)');
86
116
  console.log(' --host DB host (default: DB_HOST env or localhost)');
87
117
  console.log(' --user DB user (default: DB_USER env or root)');
88
118
  console.log(' --password DB password (default: DB_PASSWORD env)');
119
+ console.log('');
120
+ console.log('Migrations are hand-written .sql files (see migrations/), applied once,');
121
+ console.log('no down(). 0007_seed_super_admin.sql requires SUPER_ADMIN_PASSWORD in env.');
89
122
  }
90
123
  }
91
124
 
package/bin/server.js CHANGED
@@ -1,35 +1,17 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
- * Standalone auth server entry point.
5
- * Designed to be forked as a child process or run directly.
4
+ * Standalone auth service. Reads AUTH_* env — the LAUNCHER provides it; this bin
5
+ * reads process.env only and loads no .env of its own. The consuming app does:
6
+ * "start-auth": "dotenv -e development.env -- xeplr-auth-server"
6
7
  *
7
- * Reads config from AUTH_CONFIG env var (JSON) or individual env vars.
8
+ * env: ENCRYPTION_KEY · AUTH_DB_CONNECTION_INFO_ENCRYPTED · AUTH_DB_NAME ·
9
+ * AUTH_JWT_SECRET · AUTH_PORT · AUTH_ACTIVATION_BASE_URL ·
10
+ * AUTH_ACCESS_TOKEN_TTL_MINUTES · XEPLR_AUTH_MIGRATIONS · email vars · REDIS_*
8
11
  */
9
- var auth = require('../index');
10
-
11
- var config = {};
12
-
13
- if (process.env.AUTH_CONFIG) {
14
- try {
15
- config = JSON.parse(process.env.AUTH_CONFIG);
16
- } catch (e) {
17
- console.error('Failed to parse AUTH_CONFIG:', e.message);
18
- process.exit(1);
19
- }
20
- }
21
-
22
- config.port = config.port || process.env.AUTH_PORT || 19001;
23
- config.database = config.database || process.env.DB_AUTH || 'architects_auth';
24
-
25
- if (process.env.JWT_SECRET) {
26
- config.jwt = config.jwt || {};
27
- config.jwt.secret = config.jwt.secret || process.env.JWT_SECRET;
28
- }
29
-
30
- auth.start(config);
31
-
32
- // Notify parent process (if forked) that auth is ready
33
- if (process.send) {
34
- process.send({ status: 'ready', port: config.port });
35
- }
12
+ require('../index').boot().then(function () {
13
+ if (process.send) process.send({ status: 'ready' });
14
+ }).catch(function (err) {
15
+ console.error('[auth] startup failed:', err.stack || err.message);
16
+ process.exit(1);
17
+ });
package/index.js CHANGED
@@ -1,12 +1,23 @@
1
1
  const path = require('path');
2
- const { getConnection, bindModels, resolveConfig, migrator } = require('@xeplr/db');
2
+ const { getConnection, bindModels, resolveConfig, sqlMigrator, resolveDbConnection, describeDbConnection, migrationsFor, migrationsVar } = require('@xeplr/db');
3
+
4
+ // The connection comes from XEPLR_DB_CONNECTION unless this service overrides
5
+ // it with AUTH_DB_CONNECTION_INFO_ENCRYPTED — one server, one credential, with
6
+ // an escape hatch. AUTH_DB_NAME is untouched: which DATABASE is a separate
7
+ // question from where the server is, and auth keeps its own.
8
+ var AUTH_CONN_VAR = 'AUTH_DB_CONNECTION_INFO_ENCRYPTED';
9
+ function authConnection() { return resolveDbConnection(AUTH_CONN_VAR); }
3
10
  const { createApp } = require('@xeplr/base-apis');
4
11
  const authHelper = require('./lib/authHelper');
12
+ const { seedSuperAdmin } = require('./lib/seed');
5
13
  const authMiddleware = require('./lib/authMiddleware');
6
14
  const authService = require('./lib/authService');
7
15
  const accessService = require('./lib/accessService');
8
16
  const { accessMiddleware, requireRole } = require('./lib/accessMiddleware');
9
17
  const sessionService = require('./lib/sessionService');
18
+ const ticketService = require('./lib/ticketService');
19
+ const hooks = require('./lib/hooks');
20
+ const mtMembershipMiddleware = require('./lib/mtMembershipMiddleware');
10
21
  const createAuthRouter = require('./lib/authRouter');
11
22
  const createAdminRouter = require('./lib/adminRouter');
12
23
  const models = require('./models');
@@ -23,12 +34,12 @@ let _initialized = false;
23
34
  * @param {object} [config.email] - Email config { provider, smtp, aws, azure, brevo }
24
35
  * @param {string} [config.resetBaseUrl] - Base URL for password reset links
25
36
  */
26
- function init(config = {}) {
27
- // Setup database connection
28
- const dbName = config.database || process.env.DB_AUTH || 'auth';
29
- const dbOptions = Object.assign({ connectionName: config.connectionName || 'auth' }, config.db || {});
30
- const connection = getConnection(dbName, dbOptions);
31
- bindModels(connection);
37
+ async function init(config = {}) {
38
+ // One call: decrypt the app-supplied connection, connect, and bind the models.
39
+ const dbName = config.database || process.env.AUTH_DB_NAME || 'auth';
40
+ const connection = await getConnection(dbName, config.connection || config.db, {
41
+ connectionName: config.connectionName || 'auth'
42
+ });
32
43
 
33
44
  // Configure JWT
34
45
  if (config.jwt) {
@@ -78,8 +89,8 @@ function adminRouter() {
78
89
  * @param {Function[]} [config.middleware] - Additional middleware
79
90
  * @returns {http.Server}
80
91
  */
81
- function start(config = {}) {
82
- init(config);
92
+ async function start(config = {}) {
93
+ await init(config);
83
94
 
84
95
  const port = config.port || process.env.AUTH_PORT || 19001;
85
96
  const authRouter = createAuthRouter({ resetBaseUrl: config.resetBaseUrl });
@@ -93,63 +104,188 @@ function start(config = {}) {
93
104
  }
94
105
 
95
106
  /**
96
- * Boot xeplr-auth as a fully independent service.
97
- * Resolves DB config, runs migrations/seeds, and starts the server.
107
+ * Boot xeplr-auth as a fully independent service. ENV-DRIVEN — no config; there
108
+ * is always one auth system, so it reads everything from AUTH_* env. Resolves the
109
+ * connection, self-migrates, configures email/jwt/activation, starts the server.
98
110
  *
99
- * @param {object} config
100
- * @param {number|string} config.port - Port to listen on (default: AUTH_PORT env or 19001)
101
- * @param {string} config.database - Database name for auth tables
102
- * @param {string} [config.connectionName] - Named connection identifier (default: 'auth')
103
- * @param {object} [config.db] - DB connection options { host, user, password, port }
104
- * @param {object} [config.jwt] - JWT options { secret, accessTokenExpiresIn }
105
- * @param {object} [config.email] - Email config { provider, smtp, aws, azure, brevo }
106
- * @param {string} [config.resetBaseUrl] - Base URL for reset links
107
- * @param {string} [config.migrationsDir] - Additional project-specific migrations directory
108
- * @param {string} [config.seedsDir] - Additional project-specific seeds directory
109
- * @param {object} [config.corsOptions] - CORS options
110
- * @param {Function[]} [config.middleware] - Additional middleware
111
111
  * @returns {Promise<http.Server>}
112
112
  */
113
- async function boot(config = {}) {
114
- var connName = config.connectionName || 'auth';
115
- var database = config.database || process.env.DB_AUTH || 'auth';
116
- var authPkgDir = path.join(__dirname);
113
+ // Boot the auth service. ENV-DRIVEN, no config there is always a single auth
114
+ // system, so it reads its whole setup from AUTH_* env (see requiredEnv + README):
115
+ // connection AUTH_DB_CONNECTION_INFO_ENCRYPTED · db AUTH_DB_NAME · AUTH_JWT_SECRET
116
+ // AUTH_PORT · AUTH_ACTIVATION_BASE_URL · AUTH_ACCESS_TOKEN_TTL_MINUTES
117
+ // XEPLR_AUTH_MIGRATIONS · email vars · REDIS_*
118
+ async function boot() {
119
+ var connName = 'auth';
120
+ var database = process.env.AUTH_DB_NAME || 'auth';
117
121
 
118
- await resolveConfig(connName);
122
+ await resolveConfig(connName, authConnection());
119
123
 
120
- // Run migrations
121
- var migrationResult = await migrator.up({
124
+ // Self-migrate: auth's own .sql migrations (schema + base data), then the
125
+ // app's extension dir. Idempotent — same as `xeplr-auth-migrate up`.
126
+ var migrationResult = await sqlMigrator.up({
122
127
  db: database,
123
- dir: path.join(authPkgDir, 'migrations'),
124
- extDir: config.migrationsDir || null,
128
+ dir: path.join(__dirname, 'migrations'),
129
+ // The SHARED convention, not this library's own reading of the variable.
130
+ // Passing process.env.XEPLR_AUTH_MIGRATIONS straight through is what
131
+ // broke here: a comma-separated list became one nonexistent directory and
132
+ // this entry point silently applied none of BI's, workflow's or jobs'
133
+ // migrations, while the xeplr-auth-migrate CLI — which had its own
134
+ // splitting — applied all of them. Same variable, two behaviours.
135
+ extDir: migrationsFor('auth'),
125
136
  type: 'precede',
126
137
  connectionName: connName
127
138
  });
139
+ console.log(migrationResult.migrations.length
140
+ ? '[xeplr-auth] ran ' + migrationResult.migrations.length + ' migrations'
141
+ : '[xeplr-auth] migrations up to date');
128
142
 
129
- if (migrationResult.migrations.length > 0) {
130
- console.log('[xeplr-auth] ran ' + migrationResult.migrations.length + ' migrations');
131
- } else {
132
- console.log('[xeplr-auth] migrations up to date');
133
- }
143
+ // Configure email (via @xeplr/utils, the engine auth uses) + activation links.
144
+ require('@xeplr/utils').configureFromEnv();
145
+ authService.configureActivation({
146
+ // Fully qualified, one per link — the token is the only thing appended.
147
+ // AUTH_ACTIVATION_BASE_URL still works as an origin; see configureActivation.
148
+ activationUrl: process.env.AUTH_ACTIVATION_URL || null,
149
+ inviteUrl: process.env.AUTH_INVITE_URL || null,
150
+ baseUrl: process.env.AUTH_ACTIVATION_BASE_URL || null
151
+ });
134
152
 
135
- // Run seeds (auth's own seeds always run, project seeds if provided)
136
- var seedResult = await migrator.seed({
137
- db: database,
138
- seedsDir: path.join(authPkgDir, 'seeds'),
139
- extSeedsDir: config.seedsDir || null,
140
- type: 'precede',
141
- connectionName: connName
153
+ var server = await start({
154
+ database: database,
155
+ connectionName: connName,
156
+ connection: authConnection(),
157
+ port: process.env.AUTH_PORT,
158
+ jwt: {
159
+ secret: process.env.AUTH_JWT_SECRET,
160
+ accessTokenExpiresIn: (process.env.AUTH_ACCESS_TOKEN_TTL_MINUTES || '15') + 'm'
161
+ }
142
162
  });
143
163
 
144
- if (seedResult.length > 0) {
145
- console.log('[xeplr-auth] ran ' + seedResult.length + ' seeds');
146
- }
164
+ banner();
165
+ return server;
166
+ }
147
167
 
148
- return start(Object.assign({ database, connectionName: connName }, config));
168
+ // Startup summary the EFFECTIVE config (defaults resolved) so you can see what
169
+ // the service is actually running with. Secrets are masked, never printed.
170
+ function banner() {
171
+ var set = function (v) { return v ? '✓ set' : '✗ MISSING'; };
172
+ var rows = [
173
+ ['port', process.env.AUTH_PORT || '19001'],
174
+ ['database', process.env.AUTH_DB_NAME || 'auth'],
175
+ [migrationsVar('auth'), migrationsFor('auth') || '(none)'],
176
+ ['activation url', process.env.AUTH_ACTIVATION_URL || process.env.AUTH_ACTIVATION_BASE_URL || '(none)'],
177
+ ['invite url', process.env.AUTH_INVITE_URL || '(derived)'],
178
+ ['access token ttl', (process.env.AUTH_ACCESS_TOKEN_TTL_MINUTES || '15') + 'm'],
179
+ ['refresh token ttl', (process.env.AUTH_REFRESH_TOKEN_TTL_DAYS || '7') + 'd'],
180
+ ['slide tolerance', (process.env.AUTH_ACCESS_TOKEN_TOLERANCE_SECONDS || '0') + 's'],
181
+ ['max sessions/user', process.env.AUTH_MAX_SESSIONS_PER_USER || '5'],
182
+ ['redis', (process.env.REDIS_HOST || 'localhost') + ':' + (process.env.REDIS_PORT || '6379')],
183
+ ['email', process.env.EMAIL_PROVIDER || '(not configured)'],
184
+ // Names the VARIABLE it resolved through, not just "set" — with a shared
185
+ // default and a per-service override, "which server am I on" is otherwise
186
+ // a guess.
187
+ ['connection', describeDbConnection(AUTH_CONN_VAR) || '✗ NOT CONFIGURED'],
188
+ ['encryption key', set(process.env.ENCRYPTION_KEY)],
189
+ ['jwt secret', process.env.AUTH_JWT_SECRET ? '✓ set' : '✗ INSECURE DEFAULT']
190
+ ];
191
+ var lines = rows.map(function (r) { return r[0].padEnd(19) + String(r[1]); });
192
+ var title = 'xeplr-auth · running';
193
+ var inner = Math.max.apply(null, [title.length].concat(lines.map(function (l) { return l.length; }))) + 2;
194
+ var bar = '─'.repeat(inner);
195
+ console.log('\n┌' + bar + '┐');
196
+ console.log('│ ' + title.padEnd(inner - 1) + '│');
197
+ console.log('├' + bar + '┤');
198
+ lines.forEach(function (l) { console.log('│ ' + l.padEnd(inner - 1) + '│'); });
199
+ console.log('└' + bar + '┘\n');
149
200
  }
150
201
 
202
+ /**
203
+ * Attach to the auth database from ANOTHER process (e.g. the api service), so it
204
+ * can act on auth tables — invite users, assign roles. ENV-DRIVEN
205
+ * (AUTH_DB_NAME + AUTH_DB_CONNECTION_INFO_ENCRYPTED). Opens a SECONDARY connection
206
+ * (bind:false, so it never hijacks the api's global model binding) and hands back
207
+ * model/service accessors.
208
+ *
209
+ * const auth = require('@xeplr/auth').attach();
210
+ * await auth.ready();
211
+ * await auth.model('User').query()... // auth-package model bound to the auth DB
212
+ * auth.service.invite(...) // authService
213
+ *
214
+ * @returns {{ ready: () => Promise, conn: () => Knex, model: (name:string) => Model, service: object }}
215
+ */
216
+ function attach() {
217
+ var _conn = null;
218
+ var _ready = (async function () {
219
+ _conn = await getConnection(
220
+ process.env.AUTH_DB_NAME || 'auth',
221
+ authConnection(),
222
+ { bind: false, connectionName: 'auth' }
223
+ );
224
+ authService.configureActivation({
225
+ // Fully qualified, one per link — the token is the only thing appended.
226
+ // AUTH_ACTIVATION_BASE_URL still works as an origin; see configureActivation.
227
+ activationUrl: process.env.AUTH_ACTIVATION_URL || null,
228
+ inviteUrl: process.env.AUTH_INVITE_URL || null,
229
+ baseUrl: process.env.AUTH_ACTIVATION_BASE_URL || null
230
+ });
231
+ return _conn;
232
+ })();
233
+
234
+ function conn() {
235
+ if (!_conn) throw new Error('auth.attach(): connection not ready — await ready() first');
236
+ return _conn;
237
+ }
238
+ function model(name) {
239
+ var M = models[name];
240
+ if (!M) throw new Error('auth.attach(): unknown auth model "' + name + '"');
241
+ return M.bindKnex(conn());
242
+ }
243
+ return { ready: function () { return _ready; }, conn: conn, model: model, service: authService };
244
+ }
245
+
246
+ // Env vars this library needs — apps spread this into their env.required.js so
247
+ // the names live here (change once, every app picks it up), not re-listed per app.
248
+ var requiredEnv = [
249
+ 'ENCRYPTION_KEY',
250
+ 'AUTH_JWT_SECRET',
251
+ // NOT listed literally: the activation link can be given either as
252
+ // AUTH_ACTIVATION_URL (fully qualified, preferred) or as the older
253
+ // AUTH_ACTIVATION_BASE_URL (an origin). checkEnv takes a list of names and
254
+ // demands every one, which would refuse to boot an install that configured
255
+ // the new form. The getter below asks for whichever is actually in use.
256
+ 'AUTH_PORT',
257
+ 'AUTH_DB_NAME', // auth db (api reaches it via attach())
258
+ 'XEPLR_AUTH_MIGRATIONS', // migrations run via migrate:up
259
+ ];
260
+
261
+ // Read at ACCESS time so it reflects the .env the app has already loaded — the
262
+ // same reason @xeplr/email's requiredEnv is a getter. Names the form the
263
+ // install is actually using, and asks for the new one when neither is set.
264
+ Object.defineProperty(requiredEnv, 'activationLinkVar', {
265
+ enumerable: false,
266
+ get: function () {
267
+ return process.env.AUTH_ACTIVATION_BASE_URL && !process.env.AUTH_ACTIVATION_URL
268
+ ? 'AUTH_ACTIVATION_BASE_URL'
269
+ : 'AUTH_ACTIVATION_URL';
270
+ }
271
+ });
272
+
273
+ // NOT in requiredEnv, deliberately: AUTH_DB_CONNECTION_INFO_ENCRYPTED is now an
274
+ // OVERRIDE. The connection normally comes from the shared XEPLR_DB_CONNECTION,
275
+ // so demanding the auth-specific name would fail a correctly configured
276
+ // install. Missing-ness is caught at the point of use by resolveDbConnection,
277
+ // which names both variables.
278
+ //
279
+ // AUTH_DB_NAME stays required and stays auth's own — the DATABASE is a
280
+ // different question from the SERVER, and services do not share one.
281
+
151
282
  module.exports = {
283
+ // Hand in @xeplr-workflow/api's resumeByKey to release a step that is
284
+ // waiting for someone to activate. Omitted, activation just activates.
285
+ configureWorkflowResume: authService.configureWorkflowResume,
286
+ requiredEnv,
152
287
  init,
288
+ attach,
153
289
  router,
154
290
  adminRouter,
155
291
  start,
@@ -157,9 +293,13 @@ module.exports = {
157
293
  authMiddleware,
158
294
  accessMiddleware,
159
295
  requireRole,
296
+ mtMembershipMiddleware,
160
297
  authHelper,
298
+ seedSuperAdmin,
161
299
  authService,
162
300
  accessService,
163
301
  sessionService,
302
+ ticketService,
303
+ hooks,
164
304
  models
165
305
  };