@logto/cli 1.42.0 → 1.44.0
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.
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createPoolAndDatabaseIfNeeded } from '../../../database.js';
|
|
2
|
+
import { assertNoExistingTenantRoles, getDatabaseName } from '../../../queries/database.js';
|
|
2
3
|
import { doesConfigsTableExist } from '../../../queries/logto-config.js';
|
|
3
4
|
import { consoleLog, oraPromise } from '../../../utils.js';
|
|
4
5
|
import { getLatestAlterationTimestamp } from '../alteration/index.js';
|
|
@@ -6,6 +7,10 @@ import { getAlterationDirectory } from '../alteration/utils.js';
|
|
|
6
7
|
import { disableAdminPwnedPasswordCheckDescription } from './options.js';
|
|
7
8
|
import { createTables, seedCloud, seedTables, seedTest } from './tables.js';
|
|
8
9
|
export const seedByPool = async (pool, { cloud = false, test = false, encryptBaseRole = false, disablePwnedPasswordCheck = false, } = {}) => {
|
|
10
|
+
// Roles left behind by an old installation are a cluster-level precondition, so fail here before
|
|
11
|
+
// opening the transaction.
|
|
12
|
+
const database = await getDatabaseName(pool, true);
|
|
13
|
+
await assertNoExistingTenantRoles(pool, database);
|
|
9
14
|
await pool.transaction(async (connection) => {
|
|
10
15
|
// Check alteration scripts available in order to insert correct timestamp
|
|
11
16
|
const latestTimestamp = await getLatestAlterationTimestamp();
|
package/lib/package-json.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export const packageJson = {
|
|
2
2
|
"name": "@logto/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.44.0",
|
|
4
4
|
"description": "Logto CLI.",
|
|
5
5
|
"author": "Silverhand Inc. <contact@silverhand.io>",
|
|
6
6
|
"homepage": "https://github.com/logto-io/logto#readme",
|
|
@@ -74,14 +74,14 @@ export const packageJson = {
|
|
|
74
74
|
"@types/sinon": "^17.0.0",
|
|
75
75
|
"@types/tar": "^6.1.12",
|
|
76
76
|
"@types/yargs": "^17.0.13",
|
|
77
|
-
"@vitest/coverage-v8": "^4.1.
|
|
77
|
+
"@vitest/coverage-v8": "^4.1.11",
|
|
78
78
|
"@withtyped/server": "^0.14.0",
|
|
79
79
|
"eslint": "^8.56.0",
|
|
80
80
|
"lint-staged": "^15.0.0",
|
|
81
81
|
"prettier": "^3.5.3",
|
|
82
82
|
"sinon": "^19.0.0",
|
|
83
83
|
"typescript": "^5.5.3",
|
|
84
|
-
"vitest": "^4.1.
|
|
84
|
+
"vitest": "^4.1.11"
|
|
85
85
|
},
|
|
86
86
|
"eslintConfig": {
|
|
87
87
|
"extends": "@silverhand",
|
|
@@ -1,2 +1,11 @@
|
|
|
1
1
|
import type { CommonQueryMethods } from '@silverhand/slonik';
|
|
2
2
|
export declare const getDatabaseName: (pool: CommonQueryMethods, normalized?: boolean) => Promise<string>;
|
|
3
|
+
/**
|
|
4
|
+
* Check the roles that a fresh Logto database needs before the seed opens its transaction.
|
|
5
|
+
*
|
|
6
|
+
* PostgreSQL roles are shared by the whole cluster, so dropping a database does not remove the
|
|
7
|
+
* roles that were created for it. Reusing an existing role could change the permissions of another
|
|
8
|
+
* Logto installation, therefore the seed command must stop and let an administrator inspect the
|
|
9
|
+
* roles instead.
|
|
10
|
+
*/
|
|
11
|
+
export declare const assertNoExistingTenantRoles: (pool: CommonQueryMethods, database: string) => Promise<void>;
|
package/lib/queries/database.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { createTenantDatabaseMetadata } from '@logto/core-kit';
|
|
2
|
+
import { adminTenantId, defaultTenantId } from '@logto/schemas';
|
|
1
3
|
import { sql } from '@silverhand/slonik';
|
|
2
4
|
export const getDatabaseName = async (pool, normalized = false) => {
|
|
3
5
|
const { currentDatabase } = await pool.one(sql `
|
|
@@ -5,3 +7,33 @@ export const getDatabaseName = async (pool, normalized = false) => {
|
|
|
5
7
|
`);
|
|
6
8
|
return normalized ? currentDatabase.replaceAll('-', '_') : currentDatabase;
|
|
7
9
|
};
|
|
10
|
+
/**
|
|
11
|
+
* Check the roles that a fresh Logto database needs before the seed opens its transaction.
|
|
12
|
+
*
|
|
13
|
+
* PostgreSQL roles are shared by the whole cluster, so dropping a database does not remove the
|
|
14
|
+
* roles that were created for it. Reusing an existing role could change the permissions of another
|
|
15
|
+
* Logto installation, therefore the seed command must stop and let an administrator inspect the
|
|
16
|
+
* roles instead.
|
|
17
|
+
*/
|
|
18
|
+
export const assertNoExistingTenantRoles = async (pool, database) => {
|
|
19
|
+
// Derive the names from the same helper `createTenant()` uses at seed time, so this check keeps
|
|
20
|
+
// matching if the naming scheme ever changes.
|
|
21
|
+
const defaultTenant = createTenantDatabaseMetadata(database, defaultTenantId);
|
|
22
|
+
const adminTenant = createTenantDatabaseMetadata(database, adminTenantId);
|
|
23
|
+
const roleNames = [defaultTenant.parentRole, defaultTenant.role, adminTenant.role];
|
|
24
|
+
const existingRoles = await pool.any(sql `
|
|
25
|
+
select rolname as "roleName"
|
|
26
|
+
from pg_roles
|
|
27
|
+
where rolname in (${sql.join(roleNames.map((roleName) => sql `${roleName}`), sql `, `)})
|
|
28
|
+
order by rolname
|
|
29
|
+
`);
|
|
30
|
+
if (existingRoles.length > 0) {
|
|
31
|
+
throw new Error([
|
|
32
|
+
`Cannot seed database "${database}" because these PostgreSQL roles already exist:`,
|
|
33
|
+
...existingRoles.map(({ roleName }) => ` - ${roleName}`),
|
|
34
|
+
'',
|
|
35
|
+
'PostgreSQL roles are cluster-wide and are not removed when a database is dropped.',
|
|
36
|
+
'Verify that the roles belong to an old Logto installation, remove them as a PostgreSQL administrator, and run the seed command again.',
|
|
37
|
+
].join('\n'));
|
|
38
|
+
}
|
|
39
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { createMockPool, createMockQueryResult, sql } from '@silverhand/slonik';
|
|
2
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
3
|
+
import { expectSqlAssert } from '../test-utils.js';
|
|
4
|
+
import { assertNoExistingTenantRoles, getDatabaseName } from './database.js';
|
|
5
|
+
const mockQuery = vi.fn();
|
|
6
|
+
const pool = createMockPool({
|
|
7
|
+
query: async (query, values) => mockQuery(query, values),
|
|
8
|
+
});
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
mockQuery.mockReset();
|
|
11
|
+
});
|
|
12
|
+
describe('getDatabaseName()', () => {
|
|
13
|
+
it('returns the current database name and optionally normalizes hyphens', async () => {
|
|
14
|
+
mockQuery.mockResolvedValueOnce(createMockQueryResult([{ currentDatabase: 'logto-db' }]));
|
|
15
|
+
await expect(getDatabaseName(pool)).resolves.toBe('logto-db');
|
|
16
|
+
mockQuery.mockResolvedValueOnce(createMockQueryResult([{ currentDatabase: 'logto-db' }]));
|
|
17
|
+
await expect(getDatabaseName(pool, true)).resolves.toBe('logto_db');
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
describe('assertNoExistingTenantRoles()', () => {
|
|
21
|
+
const database = 'logto';
|
|
22
|
+
const roleNames = [
|
|
23
|
+
'logto_tenant_logto',
|
|
24
|
+
'logto_tenant_logto_default',
|
|
25
|
+
'logto_tenant_logto_admin',
|
|
26
|
+
];
|
|
27
|
+
const expectedSql = sql `
|
|
28
|
+
select rolname as "roleName"
|
|
29
|
+
from pg_roles
|
|
30
|
+
where rolname in (${sql.join(roleNames.map((roleName) => sql `${roleName}`), sql `, `)})
|
|
31
|
+
order by rolname
|
|
32
|
+
`;
|
|
33
|
+
it('resolves when none of the seed roles exist', async () => {
|
|
34
|
+
mockQuery.mockImplementationOnce(async (query, values) => {
|
|
35
|
+
expectSqlAssert(query, expectedSql.sql);
|
|
36
|
+
expect(values).toEqual(roleNames);
|
|
37
|
+
return createMockQueryResult([]);
|
|
38
|
+
});
|
|
39
|
+
await expect(assertNoExistingTenantRoles(pool, database)).resolves.toBeUndefined();
|
|
40
|
+
});
|
|
41
|
+
it('reports existing roles before the seed can create them', async () => {
|
|
42
|
+
mockQuery.mockResolvedValueOnce(createMockQueryResult([{ roleName: 'logto_tenant_logto' }]));
|
|
43
|
+
await expect(assertNoExistingTenantRoles(pool, database)).rejects.toThrow([
|
|
44
|
+
'Cannot seed database "logto" because these PostgreSQL roles already exist:',
|
|
45
|
+
' - logto_tenant_logto',
|
|
46
|
+
'',
|
|
47
|
+
'PostgreSQL roles are cluster-wide and are not removed when a database is dropped.',
|
|
48
|
+
'Verify that the roles belong to an old Logto installation, remove them as a PostgreSQL administrator, and run the seed command again.',
|
|
49
|
+
].join('\n'));
|
|
50
|
+
expect(mockQuery).toHaveBeenCalledTimes(1);
|
|
51
|
+
});
|
|
52
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@logto/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.44.0",
|
|
4
4
|
"description": "Logto CLI.",
|
|
5
5
|
"author": "Silverhand Inc. <contact@silverhand.io>",
|
|
6
6
|
"homepage": "https://github.com/logto-io/logto#readme",
|
|
@@ -45,10 +45,10 @@
|
|
|
45
45
|
"tar": "^7.5.21",
|
|
46
46
|
"yargs": "^17.6.0",
|
|
47
47
|
"zod": "3.24.3",
|
|
48
|
-
"@logto/connector-kit": "^5.1.
|
|
49
|
-
"@logto/core-kit": "^2.
|
|
50
|
-
"@logto/schemas": "1.
|
|
51
|
-
"@logto/shared": "^3.4.
|
|
48
|
+
"@logto/connector-kit": "^5.1.2",
|
|
49
|
+
"@logto/core-kit": "^2.13.0",
|
|
50
|
+
"@logto/schemas": "1.44.0",
|
|
51
|
+
"@logto/shared": "^3.4.3"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
54
|
"@silverhand/eslint-config": "6.0.1",
|
|
@@ -59,14 +59,14 @@
|
|
|
59
59
|
"@types/sinon": "^17.0.0",
|
|
60
60
|
"@types/tar": "^6.1.12",
|
|
61
61
|
"@types/yargs": "^17.0.13",
|
|
62
|
-
"@vitest/coverage-v8": "^4.1.
|
|
62
|
+
"@vitest/coverage-v8": "^4.1.11",
|
|
63
63
|
"@withtyped/server": "^0.14.0",
|
|
64
64
|
"eslint": "^8.56.0",
|
|
65
65
|
"lint-staged": "^15.0.0",
|
|
66
66
|
"prettier": "^3.5.3",
|
|
67
67
|
"sinon": "^19.0.0",
|
|
68
68
|
"typescript": "^5.5.3",
|
|
69
|
-
"vitest": "^4.1.
|
|
69
|
+
"vitest": "^4.1.11"
|
|
70
70
|
},
|
|
71
71
|
"eslintConfig": {
|
|
72
72
|
"extends": "@silverhand",
|