@loomcore/api 0.1.21 → 0.1.24
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/dist/__tests__/common-test.utils.js +13 -23
- package/dist/__tests__/models/test-item.model.d.ts +1 -1
- package/dist/__tests__/postgres-test-migrations/004-create-test-users-table.migration.js +0 -1
- package/dist/__tests__/postgres.test-database.js +6 -2
- package/dist/__tests__/test-objects.d.ts +9 -41
- package/dist/__tests__/test-objects.js +96 -63
- package/dist/controllers/auth.controller.js +5 -6
- package/dist/databases/postgres/migrations/001-create-migrations-table.migration.js +18 -20
- package/dist/databases/postgres/migrations/002-create-organizations-table.migration.d.ts +1 -3
- package/dist/databases/postgres/migrations/002-create-organizations-table.migration.js +41 -48
- package/dist/databases/postgres/migrations/003-create-users-table.migration.js +42 -47
- package/dist/databases/postgres/migrations/004-create-refresh-tokens-table.migration.js +34 -37
- package/dist/databases/postgres/migrations/005-create-meta-org.migration.js +24 -23
- package/dist/databases/postgres/migrations/006-create-admin-user.migration.d.ts +1 -2
- package/dist/databases/postgres/migrations/006-create-admin-user.migration.js +16 -7
- package/dist/databases/postgres/migrations/007-create-roles-table.migration.d.ts +21 -0
- package/dist/databases/postgres/migrations/007-create-roles-table.migration.js +62 -0
- package/dist/databases/postgres/migrations/008-create-user-roles-table.migration.d.ts +21 -0
- package/dist/databases/postgres/migrations/008-create-user-roles-table.migration.js +65 -0
- package/dist/databases/postgres/migrations/009-create-features-table.migration.d.ts +21 -0
- package/dist/databases/postgres/migrations/009-create-features-table.migration.js +62 -0
- package/dist/databases/postgres/migrations/010-create-authorizations-table.migration.d.ts +21 -0
- package/dist/databases/postgres/migrations/010-create-authorizations-table.migration.js +74 -0
- package/dist/databases/postgres/migrations/setup-for-auth.migration.js +14 -5
- package/dist/databases/postgres/migrations/setup-for-multitenant.migration.d.ts +1 -1
- package/dist/databases/postgres/migrations/setup-for-multitenant.migration.js +16 -6
- package/dist/models/refresh-token.model.d.ts +1 -1
- package/dist/services/auth.service.d.ts +3 -3
- package/dist/services/auth.service.js +13 -10
- package/dist/services/multi-tenant-api.service.js +1 -1
- package/package.json +6 -6
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { randomUUID } from "crypto";
|
|
2
|
+
import { doesTableExist } from "../utils/does-table-exist.util.js";
|
|
3
|
+
export class CreateAuthorizationsTableMigration {
|
|
4
|
+
client;
|
|
5
|
+
constructor(client) {
|
|
6
|
+
this.client = client;
|
|
7
|
+
}
|
|
8
|
+
index = 10;
|
|
9
|
+
async execute(_orgId) {
|
|
10
|
+
const _id = randomUUID().toString();
|
|
11
|
+
try {
|
|
12
|
+
await this.client.query('BEGIN');
|
|
13
|
+
const tableExists = await doesTableExist(this.client, 'authorizations');
|
|
14
|
+
if (!tableExists) {
|
|
15
|
+
await this.client.query(`
|
|
16
|
+
CREATE TABLE "authorizations" (
|
|
17
|
+
"_id" VARCHAR(255) PRIMARY KEY,
|
|
18
|
+
"_orgId" VARCHAR(255),
|
|
19
|
+
"_roleId" VARCHAR(255) NOT NULL,
|
|
20
|
+
"_featureId" VARCHAR(255) NOT NULL,
|
|
21
|
+
"startDate" TIMESTAMP,
|
|
22
|
+
"endDate" TIMESTAMP,
|
|
23
|
+
"config" JSONB,
|
|
24
|
+
"_created" TIMESTAMP NOT NULL,
|
|
25
|
+
"_createdBy" VARCHAR(255) NOT NULL,
|
|
26
|
+
"_updated" TIMESTAMP NOT NULL,
|
|
27
|
+
"_updatedBy" VARCHAR(255) NOT NULL,
|
|
28
|
+
"_deleted" TIMESTAMP,
|
|
29
|
+
"_deletedBy" VARCHAR(255),
|
|
30
|
+
CONSTRAINT "fk_authorizations_organization" FOREIGN KEY ("_orgId") REFERENCES "organizations"("_id") ON DELETE CASCADE,
|
|
31
|
+
CONSTRAINT "fk_authorizations_role" FOREIGN KEY ("_roleId") REFERENCES "roles"("_id") ON DELETE CASCADE,
|
|
32
|
+
CONSTRAINT "fk_authorizations_feature" FOREIGN KEY ("_featureId") REFERENCES "features"("_id") ON DELETE CASCADE,
|
|
33
|
+
CONSTRAINT "uk_authorizations" UNIQUE ("_orgId", "_roleId", "_featureId")
|
|
34
|
+
)
|
|
35
|
+
`);
|
|
36
|
+
}
|
|
37
|
+
const result = await this.client.query(`
|
|
38
|
+
INSERT INTO "migrations" ("_id", "index", "hasRun", "reverted")
|
|
39
|
+
VALUES ('${_id}', ${this.index}, TRUE, FALSE);
|
|
40
|
+
`);
|
|
41
|
+
if (result.rowCount === 0) {
|
|
42
|
+
await this.client.query('ROLLBACK');
|
|
43
|
+
return { success: false, error: new Error(`Error inserting migration ${this.index} to migrations table: No row returned`) };
|
|
44
|
+
}
|
|
45
|
+
await this.client.query('COMMIT');
|
|
46
|
+
return { success: true, error: null };
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
await this.client.query('ROLLBACK');
|
|
50
|
+
return { success: false, error: new Error(`Error executing migration ${this.index}: ${error.message}`) };
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
async revert() {
|
|
54
|
+
try {
|
|
55
|
+
await this.client.query('BEGIN');
|
|
56
|
+
await this.client.query(`
|
|
57
|
+
DROP TABLE "authorizations";
|
|
58
|
+
`);
|
|
59
|
+
const updateResult = await this.client.query(`
|
|
60
|
+
UPDATE "migrations" SET "reverted" = TRUE WHERE "index" = '${this.index}';
|
|
61
|
+
`);
|
|
62
|
+
if (updateResult.rowCount === 0) {
|
|
63
|
+
await this.client.query('ROLLBACK');
|
|
64
|
+
return { success: false, error: new Error(`Error updating migration record for index ${this.index}: No row returned`) };
|
|
65
|
+
}
|
|
66
|
+
await this.client.query('COMMIT');
|
|
67
|
+
return { success: true, error: null };
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
await this.client.query('ROLLBACK');
|
|
71
|
+
return { success: false, error: new Error(`Error reverting migration ${this.index}: ${error.message}`) };
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -3,7 +3,10 @@ import { CreateMigrationTableMigration } from "./001-create-migrations-table.mig
|
|
|
3
3
|
import { CreateUsersTableMigration } from "./003-create-users-table.migration.js";
|
|
4
4
|
import { doesTableExist } from "../utils/does-table-exist.util.js";
|
|
5
5
|
import { CreateAdminUserMigration } from "./006-create-admin-user.migration.js";
|
|
6
|
-
import {
|
|
6
|
+
import { CreateUserRolesTableMigration } from "./008-create-user-roles-table.migration.js";
|
|
7
|
+
import { CreateRoleTableMigration } from "./007-create-roles-table.migration.js";
|
|
8
|
+
import { CreateAuthorizationsTableMigration } from "./010-create-authorizations-table.migration.js";
|
|
9
|
+
import { CreateFeaturesTableMigration } from "./009-create-features-table.migration.js";
|
|
7
10
|
export async function setupDatabaseForAuth(client, adminUsername, adminPassword) {
|
|
8
11
|
let runMigrations = [];
|
|
9
12
|
if (await doesTableExist(client, 'migrations')) {
|
|
@@ -23,10 +26,16 @@ export async function setupDatabaseForAuth(client, adminUsername, adminPassword)
|
|
|
23
26
|
migrationsToRun.push(new CreateUsersTableMigration(client));
|
|
24
27
|
if (!runMigrations.includes(4))
|
|
25
28
|
migrationsToRun.push(new CreateRefreshTokenTableMigration(client));
|
|
26
|
-
if (!runMigrations.includes(6))
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
29
|
+
if (!runMigrations.includes(6))
|
|
30
|
+
migrationsToRun.push(new CreateAdminUserMigration(client, adminUsername, adminPassword));
|
|
31
|
+
if (!runMigrations.includes(7))
|
|
32
|
+
migrationsToRun.push(new CreateRoleTableMigration(client));
|
|
33
|
+
if (!runMigrations.includes(8))
|
|
34
|
+
migrationsToRun.push(new CreateUserRolesTableMigration(client));
|
|
35
|
+
if (!runMigrations.includes(9))
|
|
36
|
+
migrationsToRun.push(new CreateFeaturesTableMigration(client));
|
|
37
|
+
if (!runMigrations.includes(10))
|
|
38
|
+
migrationsToRun.push(new CreateAuthorizationsTableMigration(client));
|
|
30
39
|
for (const migration of migrationsToRun) {
|
|
31
40
|
await migration.execute();
|
|
32
41
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Client } from "pg";
|
|
2
|
-
export declare function setupDatabaseForMultitenant(client: Client,
|
|
2
|
+
export declare function setupDatabaseForMultitenant(client: Client, metaOrgName: string, metaOrgCode: string): Promise<{
|
|
3
3
|
success: boolean;
|
|
4
4
|
metaOrgId: string | undefined;
|
|
5
5
|
error: Error | null;
|
|
@@ -2,13 +2,12 @@ import { CreateMigrationTableMigration } from "./001-create-migrations-table.mig
|
|
|
2
2
|
import { CreateOrganizationsTableMigration } from "./002-create-organizations-table.migration.js";
|
|
3
3
|
import { doesTableExist } from "../utils/does-table-exist.util.js";
|
|
4
4
|
import { CreateMetaOrgMigration } from "./005-create-meta-org.migration.js";
|
|
5
|
-
import { randomUUID } from 'crypto';
|
|
6
5
|
import { PostgresDatabase } from "../postgres.database.js";
|
|
7
6
|
import { OrganizationService } from "../../../services/index.js";
|
|
8
7
|
import { EmptyUserContext } from "@loomcore/common/models";
|
|
9
|
-
export async function setupDatabaseForMultitenant(client,
|
|
8
|
+
export async function setupDatabaseForMultitenant(client, metaOrgName, metaOrgCode) {
|
|
10
9
|
let runMigrations = [];
|
|
11
|
-
let metaOrgId
|
|
10
|
+
let metaOrgId;
|
|
12
11
|
if (await doesTableExist(client, 'migrations')) {
|
|
13
12
|
const migrations = await client.query(`
|
|
14
13
|
SELECT "_id", "index"
|
|
@@ -28,7 +27,7 @@ export async function setupDatabaseForMultitenant(client, orgName, orgCode) {
|
|
|
28
27
|
}
|
|
29
28
|
}
|
|
30
29
|
if (!runMigrations.includes(2)) {
|
|
31
|
-
const createOrganizationTableMigration = new CreateOrganizationsTableMigration(client
|
|
30
|
+
const createOrganizationTableMigration = new CreateOrganizationsTableMigration(client);
|
|
32
31
|
const result = await createOrganizationTableMigration.execute();
|
|
33
32
|
if (!result.success) {
|
|
34
33
|
console.log('setupDatabaseForMultitenant: error creating organizations table', result.error);
|
|
@@ -44,11 +43,22 @@ export async function setupDatabaseForMultitenant(client, orgName, orgCode) {
|
|
|
44
43
|
}
|
|
45
44
|
}
|
|
46
45
|
if (!runMigrations.includes(5)) {
|
|
47
|
-
const createMetaOrgMigration = new CreateMetaOrgMigration(client,
|
|
46
|
+
const createMetaOrgMigration = new CreateMetaOrgMigration(client, metaOrgName, metaOrgCode);
|
|
48
47
|
const result = await createMetaOrgMigration.execute();
|
|
49
48
|
if (!result.success || !result.metaOrgId) {
|
|
50
49
|
console.log('setupDatabaseForMultitenant: error creating meta org', result.error);
|
|
51
|
-
return { success: false, metaOrgId: metaOrgId, error: result.error };
|
|
50
|
+
return { success: false, metaOrgId: result.metaOrgId, error: result.error };
|
|
51
|
+
}
|
|
52
|
+
metaOrgId = result.metaOrgId;
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
if (!metaOrgId) {
|
|
56
|
+
const database = new PostgresDatabase(client);
|
|
57
|
+
const organizationService = new OrganizationService(database);
|
|
58
|
+
const metaOrg = await organizationService.getMetaOrg(EmptyUserContext);
|
|
59
|
+
if (metaOrg) {
|
|
60
|
+
metaOrgId = metaOrg._id;
|
|
61
|
+
}
|
|
52
62
|
}
|
|
53
63
|
}
|
|
54
64
|
return { success: true, metaOrgId: metaOrgId, error: null };
|
|
@@ -12,7 +12,7 @@ export declare const refreshTokenSchema: import("@sinclair/typebox").TObject<{
|
|
|
12
12
|
deviceId: import("@sinclair/typebox").TString;
|
|
13
13
|
userId: import("@sinclair/typebox").TString;
|
|
14
14
|
expiresOn: import("@sinclair/typebox").TNumber;
|
|
15
|
-
created: import("@sinclair/typebox").
|
|
15
|
+
created: import("@sinclair/typebox").TTransform<import("@sinclair/typebox").TString, Date>;
|
|
16
16
|
createdBy: import("@sinclair/typebox").TString;
|
|
17
17
|
}>;
|
|
18
18
|
export declare const refreshTokenModelSpec: import("@loomcore/common/models").IModelSpec<import("@sinclair/typebox").TSchema>;
|
|
@@ -21,7 +21,7 @@ export declare class AuthService extends MultiTenantApiService<IUser> {
|
|
|
21
21
|
} | null>;
|
|
22
22
|
getUserByEmail(email: string): Promise<IUser | null>;
|
|
23
23
|
createUser(userContext: IUserContext, user: Partial<IUser>): Promise<IUser | null>;
|
|
24
|
-
requestTokenUsingRefreshToken(
|
|
24
|
+
requestTokenUsingRefreshToken(refreshToken: string, deviceId: string): Promise<ITokenResponse | null>;
|
|
25
25
|
changeLoggedInUsersPassword(userContext: IUserContext, body: any): Promise<UpdateResult>;
|
|
26
26
|
changePassword(userContext: IUserContext, queryObject: any, password: string): Promise<UpdateResult>;
|
|
27
27
|
createNewTokens(userContext: IUserContext, activeRefreshToken: IRefreshToken): Promise<{
|
|
@@ -29,8 +29,8 @@ export declare class AuthService extends MultiTenantApiService<IUser> {
|
|
|
29
29
|
refreshToken: string;
|
|
30
30
|
expiresOn: number;
|
|
31
31
|
}>;
|
|
32
|
-
getActiveRefreshToken(
|
|
33
|
-
createNewRefreshToken(userId: string, deviceId: string,
|
|
32
|
+
getActiveRefreshToken(refreshToken: string, deviceId: string): Promise<IRefreshToken | null>;
|
|
33
|
+
createNewRefreshToken(userId: string, deviceId: string, orgId?: string): Promise<IRefreshToken | null>;
|
|
34
34
|
sendResetPasswordEmail(emailAddress: string): Promise<void>;
|
|
35
35
|
resetPassword(email: string, passwordResetToken: string, password: string): Promise<UpdateResult>;
|
|
36
36
|
deleteRefreshTokensForDevice(deviceId: string): Promise<import("../databases/models/delete-result.js").DeleteResult>;
|
|
@@ -44,7 +44,7 @@ export class AuthService extends MultiTenantApiService {
|
|
|
44
44
|
async logUserIn(userContext, deviceId) {
|
|
45
45
|
const payload = userContext;
|
|
46
46
|
const accessToken = this.generateJwt(payload);
|
|
47
|
-
const refreshTokenObject = await this.createNewRefreshToken(userContext.user._id, deviceId,
|
|
47
|
+
const refreshTokenObject = await this.createNewRefreshToken(userContext.user._id, deviceId, userContext._orgId);
|
|
48
48
|
const accessTokenExpiresOn = this.getExpiresOnFromSeconds(config.auth.jwtExpirationInSeconds);
|
|
49
49
|
let loginResponse = null;
|
|
50
50
|
if (refreshTokenObject) {
|
|
@@ -88,10 +88,16 @@ export class AuthService extends MultiTenantApiService {
|
|
|
88
88
|
const createdUser = await this.create(userContext, user);
|
|
89
89
|
return createdUser;
|
|
90
90
|
}
|
|
91
|
-
async requestTokenUsingRefreshToken(
|
|
91
|
+
async requestTokenUsingRefreshToken(refreshToken, deviceId) {
|
|
92
92
|
let tokens = null;
|
|
93
|
-
const activeRefreshToken = await this.getActiveRefreshToken(
|
|
93
|
+
const activeRefreshToken = await this.getActiveRefreshToken(refreshToken, deviceId);
|
|
94
94
|
if (activeRefreshToken) {
|
|
95
|
+
const systemUserContext = getSystemUserContext();
|
|
96
|
+
const user = await this.getById(systemUserContext, activeRefreshToken.userId);
|
|
97
|
+
const userContext = {
|
|
98
|
+
_orgId: user._orgId,
|
|
99
|
+
user: user
|
|
100
|
+
};
|
|
95
101
|
tokens = await this.createNewTokens(userContext, activeRefreshToken);
|
|
96
102
|
}
|
|
97
103
|
return tokens;
|
|
@@ -121,8 +127,8 @@ export class AuthService extends MultiTenantApiService {
|
|
|
121
127
|
};
|
|
122
128
|
return tokenResponse;
|
|
123
129
|
}
|
|
124
|
-
async getActiveRefreshToken(
|
|
125
|
-
const refreshTokenResult = await this.refreshTokenService.findOne(
|
|
130
|
+
async getActiveRefreshToken(refreshToken, deviceId) {
|
|
131
|
+
const refreshTokenResult = await this.refreshTokenService.findOne(EmptyUserContext, { filters: { token: { eq: refreshToken }, deviceId: { eq: deviceId } } });
|
|
126
132
|
let activeRefreshToken = null;
|
|
127
133
|
if (refreshTokenResult) {
|
|
128
134
|
const now = Date.now();
|
|
@@ -133,8 +139,8 @@ export class AuthService extends MultiTenantApiService {
|
|
|
133
139
|
}
|
|
134
140
|
return activeRefreshToken;
|
|
135
141
|
}
|
|
136
|
-
async createNewRefreshToken(userId, deviceId,
|
|
137
|
-
const expiresOn =
|
|
142
|
+
async createNewRefreshToken(userId, deviceId, orgId) {
|
|
143
|
+
const expiresOn = this.getExpiresOnFromDays(config.auth.refreshTokenExpirationInDays);
|
|
138
144
|
const newRefreshToken = {
|
|
139
145
|
_orgId: orgId,
|
|
140
146
|
token: this.generateRefreshToken(),
|
|
@@ -236,9 +242,6 @@ export class AuthService extends MultiTenantApiService {
|
|
|
236
242
|
const hash = await passwordUtils.hashPassword(entity.password);
|
|
237
243
|
entity.password = hash;
|
|
238
244
|
}
|
|
239
|
-
if (isCreate && !entity.roles) {
|
|
240
|
-
entity.roles = ["user"];
|
|
241
|
-
}
|
|
242
245
|
const preparedEntity = await super.preprocessEntity(userContext, entity, isCreate, allowId);
|
|
243
246
|
return preparedEntity;
|
|
244
247
|
}
|
|
@@ -11,7 +11,7 @@ export class MultiTenantApiService extends GenericApiService {
|
|
|
11
11
|
}
|
|
12
12
|
}
|
|
13
13
|
prepareQuery(userContext, queryOptions, operations) {
|
|
14
|
-
if (!config?.app?.isMultiTenant) {
|
|
14
|
+
if (!config?.app?.isMultiTenant || userContext?.user?._id === 'system') {
|
|
15
15
|
return super.prepareQuery(userContext, queryOptions, operations);
|
|
16
16
|
}
|
|
17
17
|
if (!userContext || !userContext._orgId) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@loomcore/api",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.24",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Loom Core Api - An opinionated Node.js api using Typescript, Express, and MongoDb or PostgreSQL",
|
|
6
6
|
"scripts": {
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
"push": "git push",
|
|
14
14
|
"publishMe": "npm publish --access public",
|
|
15
15
|
"pub": "npm-run-all -s add commit patch build push publishMe",
|
|
16
|
-
"update-lib-versions": "npx --yes npm-check-updates -u -f @loomcore/
|
|
17
|
-
"install-updated-libs": "npm i @loomcore/
|
|
16
|
+
"update-lib-versions": "npx --yes npm-check-updates -u -f @loomcore/common",
|
|
17
|
+
"install-updated-libs": "npm i @loomcore/common",
|
|
18
18
|
"update-libs": "npm-run-all -s update-lib-versions install-updated-libs",
|
|
19
19
|
"typecheck": "tsc",
|
|
20
20
|
"test": "npm-run-all -s test:postgres test:mongodb",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"qs": "^6.14.0"
|
|
54
54
|
},
|
|
55
55
|
"peerDependencies": {
|
|
56
|
-
"@loomcore/common": "^0.0.
|
|
56
|
+
"@loomcore/common": "^0.0.27",
|
|
57
57
|
"@sinclair/typebox": "0.34.33",
|
|
58
58
|
"cookie-parser": "^1.4.6",
|
|
59
59
|
"cors": "^2.8.5",
|
|
@@ -70,17 +70,17 @@
|
|
|
70
70
|
"@types/express": "^5.0.1",
|
|
71
71
|
"@types/jsonwebtoken": "^9.0.9",
|
|
72
72
|
"@types/lodash": "^4.17.13",
|
|
73
|
+
"@types/pg": "^8.15.6",
|
|
73
74
|
"@types/qs": "^6.14.0",
|
|
74
75
|
"@types/supertest": "^6.0.3",
|
|
75
|
-
"@types/pg": "^8.15.6",
|
|
76
76
|
"@vitest/coverage-v8": "^3.0.9",
|
|
77
77
|
"cross-env": "^7.0.3",
|
|
78
78
|
"mongodb-memory-server": "^9.3.0",
|
|
79
79
|
"npm-run-all": "^4.1.5",
|
|
80
|
+
"pg-mem": "^3.0.5",
|
|
80
81
|
"rxjs": "^7.8.0",
|
|
81
82
|
"supertest": "^7.1.0",
|
|
82
83
|
"typescript": "^5.8.3",
|
|
83
|
-
"pg-mem": "^3.0.5",
|
|
84
84
|
"vite": "^6.2.5",
|
|
85
85
|
"vitest": "^3.0.9"
|
|
86
86
|
}
|