@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.
- package/bin/migrate.js +58 -25
- package/bin/server.js +12 -30
- package/index.js +188 -48
- package/lib/adminRouter.js +20 -92
- package/lib/authHelper.js +26 -3
- package/lib/authMiddleware.js +39 -13
- package/lib/authRouter.js +144 -12
- package/lib/authService.js +448 -16
- package/lib/hooks.js +50 -0
- package/lib/mtMembershipMiddleware.js +61 -0
- package/lib/seed.js +74 -0
- package/lib/sessionService.js +54 -6
- package/lib/ticketService.js +88 -0
- package/lib/tokenDecision.js +45 -0
- package/migrations/0001_extensions.sql +9 -0
- package/migrations/0002_users.sql +31 -0
- package/migrations/0003_catalog_tables.sql +82 -0
- package/migrations/0004_role_mappings.sql +78 -0
- package/migrations/0005_user_tenants_mapping.sql +39 -0
- package/migrations/0006_seed_catalog.sql +120 -0
- package/models/Api.js +3 -0
- package/models/ApisRolesMapping.js +3 -0
- package/models/Menu.js +3 -0
- package/models/MenuRolesMapping.js +3 -0
- package/models/Role.js +8 -0
- package/models/UiElement.js +3 -0
- package/models/UiElementsRolesMapping.js +3 -0
- package/models/UiPage.js +3 -0
- package/models/UiPagesRolesMapping.js +3 -0
- package/models/User.js +3 -13
- package/models/UserRolesMapping.js +3 -0
- package/models/UserTenantsMapping.js +20 -2
- package/models/index.js +0 -2
- package/package.json +26 -5
- package/migrations/0001_users.js +0 -22
- package/migrations/0002_menus.js +0 -16
- package/migrations/0003_apis.js +0 -16
- package/migrations/0004_uiPages.js +0 -16
- package/migrations/0005_uiElements.js +0 -16
- package/migrations/0006_roles.js +0 -15
- package/migrations/0007_apisRolesMapping.js +0 -16
- package/migrations/0008_uiPagesRolesMapping.js +0 -16
- package/migrations/0009_uiElementsRolesMapping.js +0 -16
- package/migrations/0010_menuRolesMapping.js +0 -16
- package/migrations/0011_userRolesMapping.js +0 -16
- package/migrations/0012_users_add_reset_token.js +0 -13
- package/migrations/0013_add_isPublic.js +0 -27
- package/migrations/0014_users_add_activation_token.js +0 -13
- package/migrations/0015_add_mt_columns.js +0 -41
- package/migrations/0016_tenants.js +0 -23
- package/migrations/0017_userTenantsMapping.js +0 -20
- package/models/Tenant.js +0 -63
- package/seeds/001_roles.js +0 -26
- package/seeds/002_apis.js +0 -70
- package/seeds/003_pages.js +0 -41
- package/seeds/004_elements.js +0 -46
- package/seeds/005_menus.js +0 -35
- package/seeds/006_default_tenant.js +0 -50
- 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,
|
|
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
|
|
10
|
-
*
|
|
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
|
|
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
|
-
//
|
|
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.
|
|
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(`
|
|
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 [--
|
|
81
|
-
console.log('
|
|
82
|
-
console.log('
|
|
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
|
|
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
|
|
5
|
-
*
|
|
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
|
-
*
|
|
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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,
|
|
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
|
-
//
|
|
28
|
-
const dbName = config.database || process.env.
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
-
//
|
|
121
|
-
|
|
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(
|
|
124
|
-
|
|
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
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
|
-
|
|
145
|
-
|
|
146
|
-
|
|
164
|
+
banner();
|
|
165
|
+
return server;
|
|
166
|
+
}
|
|
147
167
|
|
|
148
|
-
|
|
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
|
};
|