@loomcore/api 0.2.6 → 0.2.8
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.d.ts +5 -5
- package/dist/__tests__/common-test.utils.js +24 -21
- package/dist/controllers/auth.controller.d.ts +9 -7
- package/dist/controllers/auth.controller.js +69 -27
- package/dist/controllers/authorizations.controller.d.ts +3 -3
- package/dist/controllers/authorizations.controller.js +4 -4
- package/dist/controllers/roles.controller.d.ts +1 -1
- package/dist/controllers/roles.controller.js +3 -3
- package/dist/controllers/users.controller.d.ts +5 -6
- package/dist/controllers/users.controller.js +4 -15
- package/dist/databases/postgres/commands/postgres-create.command.d.ts +2 -2
- package/dist/databases/postgres/commands/postgres-create.command.js +5 -5
- package/dist/databases/postgres/postgres.database.d.ts +2 -2
- package/dist/databases/postgres/postgres.database.js +6 -8
- package/dist/middleware/is-authorized.d.ts +2 -2
- package/dist/middleware/is-authorized.js +13 -11
- package/dist/models/refresh-token.model.d.ts +1 -1
- package/dist/models/refresh-token.model.js +3 -4
- package/dist/services/generic-api-service/generic-api-service.interface.d.ts +6 -6
- package/dist/services/generic-api-service/generic-api.service.d.ts +8 -8
- package/dist/services/generic-api-service/generic-api.service.js +16 -16
- package/dist/services/index.d.ts +11 -12
- package/dist/services/index.js +11 -12
- package/dist/services/multi-tenant-api.service.js +6 -2
- package/dist/services/password-reset-token.service.d.ts +2 -3
- package/dist/services/password-reset-token.service.js +4 -9
- package/dist/services/refresh-token.service.d.ts +6 -0
- package/dist/services/refresh-token.service.js +7 -0
- package/dist/services/user.service.js +4 -4
- package/dist/utils/auth/attempt-login.util.d.ts +4 -0
- package/dist/utils/auth/attempt-login.util.js +47 -0
- package/dist/utils/auth/change-password.util.d.ts +4 -0
- package/dist/utils/auth/change-password.util.js +14 -0
- package/dist/utils/auth/create-new-refresh-token.util.d.ts +4 -0
- package/dist/utils/auth/create-new-refresh-token.util.js +29 -0
- package/dist/utils/auth/create-new-tokens.util.d.ts +3 -0
- package/dist/utils/auth/create-new-tokens.util.js +13 -0
- package/dist/utils/auth/generate-device-id.util.d.ts +1 -0
- package/dist/utils/auth/generate-device-id.util.js +4 -0
- package/dist/utils/auth/generate-refresh-token.util.d.ts +1 -0
- package/dist/utils/auth/generate-refresh-token.util.js +4 -0
- package/dist/utils/auth/get-active-refresh-token.util.d.ts +4 -0
- package/dist/utils/auth/get-active-refresh-token.util.js +19 -0
- package/dist/utils/auth/get-and-set-device-id-cookie.util.d.ts +2 -0
- package/dist/utils/auth/get-and-set-device-id-cookie.util.js +23 -0
- package/dist/utils/auth/get-auth-config.util.d.ts +2 -0
- package/dist/utils/auth/get-auth-config.util.js +8 -0
- package/dist/utils/auth/get-device-id-from-cookie.util.d.ts +2 -0
- package/dist/utils/auth/get-device-id-from-cookie.util.js +3 -0
- package/dist/utils/auth/get-expires-on-from-days.util.d.ts +1 -0
- package/dist/utils/auth/get-expires-on-from-days.util.js +3 -0
- package/dist/utils/auth/get-expires-on-from-minutes.util.d.ts +1 -0
- package/dist/utils/auth/get-expires-on-from-minutes.util.js +3 -0
- package/dist/utils/auth/get-expires-on-from-seconds.util.d.ts +1 -0
- package/dist/utils/auth/get-expires-on-from-seconds.util.js +3 -0
- package/dist/utils/auth/index.d.ts +18 -0
- package/dist/utils/auth/index.js +18 -0
- package/dist/utils/auth/log-user-in.util.d.ts +3 -0
- package/dist/utils/auth/log-user-in.util.js +18 -0
- package/dist/utils/auth/request-token-using-refresh-token.util.d.ts +3 -0
- package/dist/utils/auth/request-token-using-refresh-token.util.js +9 -0
- package/dist/utils/auth/reset-password.util.d.ts +4 -0
- package/dist/utils/auth/reset-password.util.js +50 -0
- package/dist/utils/auth/send-reset-password-email.util.d.ts +3 -0
- package/dist/utils/auth/send-reset-password-email.util.js +30 -0
- package/dist/utils/auth/update-last-logged-in.util.d.ts +3 -0
- package/dist/utils/auth/update-last-logged-in.util.js +15 -0
- package/dist/utils/index.d.ts +8 -6
- package/dist/utils/index.js +8 -6
- package/dist/utils/jwt.utils.d.ts +2 -0
- package/dist/utils/jwt.utils.js +12 -0
- package/dist/utils/password.utils.js +8 -8
- package/package.json +3 -3
- package/dist/services/auth.service.d.ts +0 -49
- package/dist/services/auth.service.js +0 -277
- package/dist/services/jwt.service.d.ts +0 -4
- package/dist/services/jwt.service.js +0 -18
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import type {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
1
|
+
import type { IEntity, IPagedResult, IQueryOptions, IUserContext } from "@loomcore/common/models";
|
|
2
|
+
import type { AppIdType } from "@loomcore/common/types";
|
|
3
|
+
import type { ValueError } from "@sinclair/typebox/errors";
|
|
4
|
+
import type { PostProcessEntityCustomFunction, PrepareQueryCustomFunction } from "../../controllers/types.js";
|
|
5
|
+
import type { DeleteResult } from "../../databases/models/delete-result.js";
|
|
6
|
+
import type { Operation } from "../../databases/operations/operation.js";
|
|
7
7
|
export interface IGenericApiService<T extends IEntity> {
|
|
8
8
|
validate(doc: any, isPartial?: boolean): ValueError[] | null;
|
|
9
9
|
validateMany(docs: any[], isPartial?: boolean): ValueError[] | null;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import type {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
1
|
+
import { type IEntity, type IModelSpec, type IPagedResult, type IQueryOptions, type IUserContext } from "@loomcore/common/models";
|
|
2
|
+
import type { AppIdType } from "@loomcore/common/types";
|
|
3
|
+
import type { ValueError } from "@sinclair/typebox/errors";
|
|
4
|
+
import type { PostProcessEntityCustomFunction, PrepareQueryCustomFunction } from "../../controllers/types.js";
|
|
5
|
+
import type { DeleteResult } from "../../databases/models/delete-result.js";
|
|
6
|
+
import type { IDatabase } from "../../databases/models/index.js";
|
|
7
|
+
import type { Operation } from "../../databases/operations/operation.js";
|
|
8
|
+
import type { IGenericApiService } from "./generic-api-service.interface.js";
|
|
9
9
|
export declare class GenericApiService<T extends IEntity> implements IGenericApiService<T> {
|
|
10
10
|
protected database: IDatabase;
|
|
11
11
|
protected pluralResourceName: string;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
import { auditForCreate } from
|
|
6
|
-
import { auditForUpdate } from
|
|
7
|
-
import {
|
|
1
|
+
import { DefaultQueryOptions, } from "@loomcore/common/models";
|
|
2
|
+
import { entityUtils } from "@loomcore/common/utils";
|
|
3
|
+
import _ from "lodash";
|
|
4
|
+
import { IdNotFoundError, ServerError } from "../../errors/index.js";
|
|
5
|
+
import { auditForCreate } from "../utils/audit-for-create.util.js";
|
|
6
|
+
import { auditForUpdate } from "../utils/audit-for-update.util.js";
|
|
7
|
+
import { stripSenderProvidedSystemProperties } from "../utils/strip-sender-provided-system-properties.util.js";
|
|
8
8
|
export class GenericApiService {
|
|
9
9
|
database;
|
|
10
10
|
pluralResourceName;
|
|
@@ -25,7 +25,7 @@ export class GenericApiService {
|
|
|
25
25
|
operations = this.prepareQuery(userContext, {}, []).operations;
|
|
26
26
|
}
|
|
27
27
|
const entities = await this.database.getAll(operations, this.pluralResourceName);
|
|
28
|
-
return entities.map(entity => {
|
|
28
|
+
return entities.map((entity) => {
|
|
29
29
|
const dbProcessed = this.postProcessEntity(userContext, entity);
|
|
30
30
|
if (postProcessEntityCustom) {
|
|
31
31
|
return postProcessEntityCustom(userContext, dbProcessed);
|
|
@@ -83,7 +83,7 @@ export class GenericApiService {
|
|
|
83
83
|
operations = this.prepareQuery(userContext, {}, []).operations;
|
|
84
84
|
}
|
|
85
85
|
const pagedResult = await this.database.get(operations, preparedOptions, this.modelSpec, this.pluralResourceName);
|
|
86
|
-
const transformedEntities = (pagedResult.entities || []).map(entity => {
|
|
86
|
+
const transformedEntities = (pagedResult.entities || []).map((entity) => {
|
|
87
87
|
const transformedEntity = this.postProcessEntity(userContext, entity);
|
|
88
88
|
if (postProcessEntityCustom) {
|
|
89
89
|
return postProcessEntityCustom(userContext, transformedEntity);
|
|
@@ -92,7 +92,7 @@ export class GenericApiService {
|
|
|
92
92
|
});
|
|
93
93
|
return {
|
|
94
94
|
...pagedResult,
|
|
95
|
-
entities: transformedEntities
|
|
95
|
+
entities: transformedEntities,
|
|
96
96
|
};
|
|
97
97
|
}
|
|
98
98
|
prepareQueryOptions(userContext, queryOptions) {
|
|
@@ -139,10 +139,10 @@ export class GenericApiService {
|
|
|
139
139
|
async createMany(userContext, entities) {
|
|
140
140
|
let createdEntities = [];
|
|
141
141
|
if (entities.length) {
|
|
142
|
-
const preparedEntities = await Promise.all(entities.map(entity => this.preProcessEntity(userContext, entity, true, true)));
|
|
142
|
+
const preparedEntities = await Promise.all(entities.map((entity) => this.preProcessEntity(userContext, entity, true, true)));
|
|
143
143
|
const insertResult = await this.database.createMany(preparedEntities, this.pluralResourceName);
|
|
144
144
|
if (insertResult.insertedIds) {
|
|
145
|
-
createdEntities = insertResult.entities.map(entity => this.postProcessEntity(userContext, entity));
|
|
145
|
+
createdEntities = insertResult.entities.map((entity) => this.postProcessEntity(userContext, entity));
|
|
146
146
|
}
|
|
147
147
|
}
|
|
148
148
|
return createdEntities;
|
|
@@ -151,10 +151,10 @@ export class GenericApiService {
|
|
|
151
151
|
if (!entities || entities.length === 0) {
|
|
152
152
|
return [];
|
|
153
153
|
}
|
|
154
|
-
const preparedEntities = await Promise.all(entities.map(entity => this.preProcessEntity(userContext, entity, false, true)));
|
|
154
|
+
const preparedEntities = await Promise.all(entities.map((entity) => this.preProcessEntity(userContext, entity, false, true)));
|
|
155
155
|
const { queryObject, operations } = this.prepareQuery(userContext, {}, []);
|
|
156
156
|
const rawUpdatedEntities = await this.database.batchUpdate(preparedEntities, operations, queryObject, this.pluralResourceName);
|
|
157
|
-
const updatedEntities = rawUpdatedEntities.map(entity => this.postProcessEntity(userContext, entity));
|
|
157
|
+
const updatedEntities = rawUpdatedEntities.map((entity) => this.postProcessEntity(userContext, entity));
|
|
158
158
|
return updatedEntities;
|
|
159
159
|
}
|
|
160
160
|
async fullUpdateById(userContext, id, entity) {
|
|
@@ -189,7 +189,7 @@ export class GenericApiService {
|
|
|
189
189
|
const { queryObject: preparedQuery, operations } = this.prepareQuery(userContext, queryObject, []);
|
|
190
190
|
const preparedEntity = await this.preProcessEntity(userContext, entity, false, true);
|
|
191
191
|
const rawUpdatedEntities = await this.database.update(preparedQuery, preparedEntity, operations, this.pluralResourceName);
|
|
192
|
-
const updatedEntities = rawUpdatedEntities.map(entity => this.postProcessEntity(userContext, entity));
|
|
192
|
+
const updatedEntities = rawUpdatedEntities.map((entity) => this.postProcessEntity(userContext, entity));
|
|
193
193
|
return updatedEntities;
|
|
194
194
|
}
|
|
195
195
|
async deleteById(userContext, id) {
|
|
@@ -207,7 +207,7 @@ export class GenericApiService {
|
|
|
207
207
|
async find(userContext, queryObject) {
|
|
208
208
|
const { queryObject: preparedQuery, operations } = this.prepareQuery(userContext, queryObject, []);
|
|
209
209
|
const rawEntities = await this.database.find(preparedQuery, this.pluralResourceName);
|
|
210
|
-
return rawEntities.map(entity => this.postProcessEntity(userContext, entity));
|
|
210
|
+
return rawEntities.map((entity) => this.postProcessEntity(userContext, entity));
|
|
211
211
|
}
|
|
212
212
|
async findOne(userContext, queryObject) {
|
|
213
213
|
const { queryObject: preparedQuery, operations } = this.prepareQuery(userContext, queryObject, []);
|
package/dist/services/index.d.ts
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
export * from
|
|
2
|
-
export * from
|
|
3
|
-
export * from
|
|
4
|
-
export * from
|
|
5
|
-
export * from
|
|
6
|
-
export * from
|
|
7
|
-
export * from
|
|
8
|
-
export * from
|
|
9
|
-
export * from
|
|
10
|
-
export * from
|
|
11
|
-
export * from
|
|
12
|
-
export * from './user.service.js';
|
|
1
|
+
export * from "./email.service.js";
|
|
2
|
+
export * from "./generic-api-service/generic-api.service.js";
|
|
3
|
+
export * from "./generic-api-service/generic-api-service.interface.js";
|
|
4
|
+
export * from "./generic-query-service/generic-query.service.js";
|
|
5
|
+
export * from "./generic-query-service/generic-query-service.interface.js";
|
|
6
|
+
export * from "./multi-tenant-api.service.js";
|
|
7
|
+
export * from "./organization.service.js";
|
|
8
|
+
export * from "./password-reset-token.service.js";
|
|
9
|
+
export * from "./refresh-token.service.js";
|
|
10
|
+
export * from "./tenant-query-decorator.js";
|
|
11
|
+
export * from "./user.service.js";
|
package/dist/services/index.js
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
export * from
|
|
2
|
-
export * from
|
|
3
|
-
export * from
|
|
4
|
-
export * from
|
|
5
|
-
export * from
|
|
6
|
-
export * from
|
|
7
|
-
export * from
|
|
8
|
-
export * from
|
|
9
|
-
export * from
|
|
10
|
-
export * from
|
|
11
|
-
export * from
|
|
12
|
-
export * from './user.service.js';
|
|
1
|
+
export * from "./email.service.js";
|
|
2
|
+
export * from "./generic-api-service/generic-api.service.js";
|
|
3
|
+
export * from "./generic-api-service/generic-api-service.interface.js";
|
|
4
|
+
export * from "./generic-query-service/generic-query.service.js";
|
|
5
|
+
export * from "./generic-query-service/generic-query-service.interface.js";
|
|
6
|
+
export * from "./multi-tenant-api.service.js";
|
|
7
|
+
export * from "./organization.service.js";
|
|
8
|
+
export * from "./password-reset-token.service.js";
|
|
9
|
+
export * from "./refresh-token.service.js";
|
|
10
|
+
export * from "./tenant-query-decorator.js";
|
|
11
|
+
export * from "./user.service.js";
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { config } from "../config/base-api-config.js";
|
|
2
2
|
import { BadRequestError } from "../errors/bad-request.error.js";
|
|
3
|
+
import { ServerError } from "../errors/index.js";
|
|
3
4
|
import { GenericApiService } from "./generic-api-service/generic-api.service.js";
|
|
4
5
|
import { TenantQueryDecorator } from "./tenant-query-decorator.js";
|
|
5
6
|
export class MultiTenantApiService extends GenericApiService {
|
|
@@ -14,9 +15,12 @@ export class MultiTenantApiService extends GenericApiService {
|
|
|
14
15
|
if (!config?.app?.isMultiTenant || userContext?.user?._id === "system") {
|
|
15
16
|
return super.prepareQuery(userContext, queryOptions, operations);
|
|
16
17
|
}
|
|
17
|
-
if (!userContext
|
|
18
|
+
if (!userContext?.organization?._id) {
|
|
18
19
|
throw new BadRequestError("A valid userContext was not provided to MultiTenantApiService.prepareQuery");
|
|
19
20
|
}
|
|
21
|
+
if (!this.tenantDecorator) {
|
|
22
|
+
throw new ServerError("A valid tenantDecorator was not provided to MultiTenantApiService.prepareQuery");
|
|
23
|
+
}
|
|
20
24
|
const queryObject = this.tenantDecorator.applyTenantToQuery(userContext, queryOptions, this.pluralResourceName);
|
|
21
25
|
return { queryObject, operations };
|
|
22
26
|
}
|
|
@@ -24,7 +28,7 @@ export class MultiTenantApiService extends GenericApiService {
|
|
|
24
28
|
if (!config?.app?.isMultiTenant) {
|
|
25
29
|
return super.preProcessEntity(userContext, entity, isCreate, allowId);
|
|
26
30
|
}
|
|
27
|
-
if (!userContext
|
|
31
|
+
if (!userContext?.organization?._id) {
|
|
28
32
|
throw new BadRequestError("A valid userContext was not provided to MultiTenantApiService.prepareEntity");
|
|
29
33
|
}
|
|
30
34
|
const preparedEntity = await super.preProcessEntity(userContext, entity, isCreate, allowId);
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { type IPasswordResetToken } from "@loomcore/common/models";
|
|
1
|
+
import { type IPasswordResetToken, type IUserContext } from "@loomcore/common/models";
|
|
2
2
|
import type { IDatabase } from "../databases/models/index.js";
|
|
3
3
|
import { MultiTenantApiService } from "./multi-tenant-api.service.js";
|
|
4
4
|
export declare class PasswordResetTokenService extends MultiTenantApiService<IPasswordResetToken> {
|
|
5
5
|
constructor(database: IDatabase);
|
|
6
|
-
createPasswordResetToken(email: string, expiresOn: number): Promise<IPasswordResetToken | null>;
|
|
7
|
-
getByEmail(email: string): Promise<IPasswordResetToken | null>;
|
|
6
|
+
createPasswordResetToken(userContext: IUserContext, email: string, expiresOn: number): Promise<IPasswordResetToken | null>;
|
|
8
7
|
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import crypto from "node:crypto";
|
|
2
|
-
import {
|
|
2
|
+
import { PasswordResetTokenSpec, } from "@loomcore/common/models";
|
|
3
3
|
import { MultiTenantApiService } from "./multi-tenant-api.service.js";
|
|
4
4
|
export class PasswordResetTokenService extends MultiTenantApiService {
|
|
5
5
|
constructor(database) {
|
|
6
6
|
super(database, "password_reset_tokens", "password_reset_token", PasswordResetTokenSpec);
|
|
7
7
|
}
|
|
8
|
-
async createPasswordResetToken(email, expiresOn) {
|
|
8
|
+
async createPasswordResetToken(userContext, email, expiresOn) {
|
|
9
9
|
const lowerCaseEmail = email.toLowerCase();
|
|
10
|
-
await this.deleteMany(
|
|
10
|
+
await this.deleteMany(userContext, {
|
|
11
11
|
filters: { email: { eq: lowerCaseEmail } },
|
|
12
12
|
});
|
|
13
13
|
const passwordResetToken = {
|
|
@@ -15,11 +15,6 @@ export class PasswordResetTokenService extends MultiTenantApiService {
|
|
|
15
15
|
token: crypto.randomBytes(40).toString("hex"),
|
|
16
16
|
expiresOn: expiresOn,
|
|
17
17
|
};
|
|
18
|
-
return super.create(
|
|
19
|
-
}
|
|
20
|
-
async getByEmail(email) {
|
|
21
|
-
return await super.findOne(getSystemUserContext(), {
|
|
22
|
-
filters: { email: { eq: email.toLowerCase() } },
|
|
23
|
-
});
|
|
18
|
+
return super.create(userContext, passwordResetToken);
|
|
24
19
|
}
|
|
25
20
|
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { IDatabase } from "../databases/models/index.js";
|
|
2
|
+
import { type IRefreshToken } from "../models/refresh-token.model.js";
|
|
3
|
+
import { MultiTenantApiService } from "./multi-tenant-api.service.js";
|
|
4
|
+
export declare class RefreshTokenService extends MultiTenantApiService<IRefreshToken> {
|
|
5
|
+
constructor(database: IDatabase);
|
|
6
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { refreshTokenModelSpec, } from "../models/refresh-token.model.js";
|
|
2
|
+
import { MultiTenantApiService } from "./multi-tenant-api.service.js";
|
|
3
|
+
export class RefreshTokenService extends MultiTenantApiService {
|
|
4
|
+
constructor(database) {
|
|
5
|
+
super(database, "refresh_tokens", "refresh_token", refreshTokenModelSpec);
|
|
6
|
+
}
|
|
7
|
+
}
|
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
import { UserSpec, } from "@loomcore/common/models";
|
|
2
2
|
import { ServerError } from "../errors/index.js";
|
|
3
|
+
import { passwordUtils } from "../utils/password.utils.js";
|
|
3
4
|
import { MultiTenantApiService } from "./multi-tenant-api.service.js";
|
|
4
5
|
export class UserService extends MultiTenantApiService {
|
|
5
6
|
constructor(database) {
|
|
6
7
|
super(database, "users", "user", UserSpec);
|
|
7
8
|
}
|
|
8
9
|
async fullUpdateById(_userContext, _id, _entity) {
|
|
9
|
-
throw new ServerError("
|
|
10
|
+
throw new ServerError("User full update is not allowed.");
|
|
10
11
|
}
|
|
11
12
|
async preProcessEntity(userContext, entity, isCreate, _allowId = false) {
|
|
12
13
|
const preparedEntity = await super.preProcessEntity(userContext, entity, isCreate);
|
|
13
14
|
if (preparedEntity.email) {
|
|
14
15
|
preparedEntity.email = preparedEntity.email.toLowerCase();
|
|
15
16
|
}
|
|
16
|
-
if (
|
|
17
|
-
|
|
18
|
-
return cleanedEntity;
|
|
17
|
+
if (preparedEntity.password) {
|
|
18
|
+
preparedEntity.password = await passwordUtils.hashPassword(preparedEntity.password);
|
|
19
19
|
}
|
|
20
20
|
return preparedEntity;
|
|
21
21
|
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type ILoginResponse } from "@loomcore/common/models";
|
|
2
|
+
import type { AppIdType } from "@loomcore/common/types";
|
|
3
|
+
import type { IDatabase } from "../../databases/models/index.js";
|
|
4
|
+
export declare function attemptLogin(database: IDatabase, email: string, password: string, deviceId: string, organizationId?: AppIdType): Promise<ILoginResponse | null>;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { EmptyUserContext, } from "@loomcore/common/models";
|
|
2
|
+
import { BadRequestError } from "../../errors/index.js";
|
|
3
|
+
import { OrganizationService } from "../../services/organization.service.js";
|
|
4
|
+
import { UserService } from "../../services/user.service.js";
|
|
5
|
+
import { getUserContextAuthorizations } from "../../services/utils/getUserContextAuthorizations.util.js";
|
|
6
|
+
import { passwordUtils } from "../password.utils.js";
|
|
7
|
+
import { logUserIn } from "./log-user-in.util.js";
|
|
8
|
+
export async function attemptLogin(database, email, password, deviceId, organizationId) {
|
|
9
|
+
const organizationService = new OrganizationService(database);
|
|
10
|
+
const organization = organizationId
|
|
11
|
+
? await organizationService.findOne(EmptyUserContext, {
|
|
12
|
+
filters: { _id: { eq: organizationId } },
|
|
13
|
+
})
|
|
14
|
+
: null;
|
|
15
|
+
if (organizationId && !organization) {
|
|
16
|
+
throw new BadRequestError("Invalid Credentials");
|
|
17
|
+
}
|
|
18
|
+
const lowerCaseEmail = email.toLowerCase();
|
|
19
|
+
const userContext = {
|
|
20
|
+
...EmptyUserContext,
|
|
21
|
+
organization: organization ?? undefined,
|
|
22
|
+
};
|
|
23
|
+
const userService = new UserService(database);
|
|
24
|
+
const user = await userService.findOne(userContext, {
|
|
25
|
+
filters: {
|
|
26
|
+
email: { eq: lowerCaseEmail },
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
if (!user) {
|
|
30
|
+
throw new BadRequestError("Invalid Credentials");
|
|
31
|
+
}
|
|
32
|
+
const passwordsMatch = await passwordUtils.comparePasswords(user.password, password);
|
|
33
|
+
if (!passwordsMatch) {
|
|
34
|
+
throw new BadRequestError("Invalid Credentials");
|
|
35
|
+
}
|
|
36
|
+
const authorizations = await getUserContextAuthorizations(database, user);
|
|
37
|
+
const authenticatedUserContext = {
|
|
38
|
+
user: user,
|
|
39
|
+
organization: organization ?? undefined,
|
|
40
|
+
authorizations: authorizations,
|
|
41
|
+
};
|
|
42
|
+
const tokens = await logUserIn(database, authenticatedUserContext, deviceId);
|
|
43
|
+
return {
|
|
44
|
+
tokens,
|
|
45
|
+
userContext: authenticatedUserContext,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { IUserContext } from "@loomcore/common/models";
|
|
2
|
+
import type { IDatabase } from "../../databases/models/index.js";
|
|
3
|
+
import type { UpdateResult } from "../../databases/models/update-result.js";
|
|
4
|
+
export declare function changePassword(database: IDatabase, userContext: IUserContext, password: string): Promise<UpdateResult>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import moment from "moment";
|
|
2
|
+
import { UserService } from "../../services/user.service.js";
|
|
3
|
+
export async function changePassword(database, userContext, password) {
|
|
4
|
+
const userService = new UserService(database);
|
|
5
|
+
const updates = {
|
|
6
|
+
password: password,
|
|
7
|
+
_lastPasswordChange: moment().utc().toDate(),
|
|
8
|
+
};
|
|
9
|
+
await userService.partialUpdateById(userContext, userContext.user._id, updates);
|
|
10
|
+
return {
|
|
11
|
+
success: true,
|
|
12
|
+
count: 1,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { AppIdType } from "@loomcore/common/types";
|
|
2
|
+
import type { IDatabase } from "../../databases/models/index.js";
|
|
3
|
+
import type { IRefreshToken } from "../../models/refresh-token.model.js";
|
|
4
|
+
export declare function createNewRefreshToken(database: IDatabase, userId: AppIdType, deviceId: string, orgId?: AppIdType): Promise<IRefreshToken>;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { getSystemUserContext } from "@loomcore/common/models";
|
|
2
|
+
import moment from "moment";
|
|
3
|
+
import { ServerError } from "../../errors/index.js";
|
|
4
|
+
import { RefreshTokenService } from "../../services/refresh-token.service.js";
|
|
5
|
+
import { generateRefreshToken } from "./generate-refresh-token.util.js";
|
|
6
|
+
import { getAuthConfig } from "./get-auth-config.util.js";
|
|
7
|
+
import { getExpiresOnFromDays } from "./get-expires-on-from-days.util.js";
|
|
8
|
+
export async function createNewRefreshToken(database, userId, deviceId, orgId) {
|
|
9
|
+
const authConfig = getAuthConfig();
|
|
10
|
+
const expiresOn = getExpiresOnFromDays(authConfig.refreshTokenExpirationInDays);
|
|
11
|
+
const newRefreshToken = {
|
|
12
|
+
_orgId: orgId,
|
|
13
|
+
token: generateRefreshToken(),
|
|
14
|
+
deviceId,
|
|
15
|
+
userId,
|
|
16
|
+
expiresOn: expiresOn,
|
|
17
|
+
created: moment().utc().toDate(),
|
|
18
|
+
createdBy: userId,
|
|
19
|
+
};
|
|
20
|
+
const refreshTokenService = new RefreshTokenService(database);
|
|
21
|
+
await refreshTokenService.deleteMany(getSystemUserContext(), {
|
|
22
|
+
filters: { deviceId: { eq: deviceId } },
|
|
23
|
+
});
|
|
24
|
+
const insertResult = await refreshTokenService.create(getSystemUserContext(), newRefreshToken);
|
|
25
|
+
if (!insertResult) {
|
|
26
|
+
throw new ServerError("Failed to create refresh token");
|
|
27
|
+
}
|
|
28
|
+
return insertResult;
|
|
29
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { ITokenResponse, IUserContext } from "@loomcore/common/models";
|
|
2
|
+
import type { IRefreshToken } from "../../models/refresh-token.model.js";
|
|
3
|
+
export declare function createNewTokens(userContext: IUserContext, activeRefreshToken: IRefreshToken): Promise<ITokenResponse>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { generateJwt } from "../jwt.utils.js";
|
|
2
|
+
import { getAuthConfig } from "./get-auth-config.util.js";
|
|
3
|
+
import { getExpiresOnFromSeconds } from "./get-expires-on-from-seconds.util.js";
|
|
4
|
+
export async function createNewTokens(userContext, activeRefreshToken) {
|
|
5
|
+
const authConfig = getAuthConfig();
|
|
6
|
+
const accessToken = generateJwt(userContext);
|
|
7
|
+
const accessTokenExpiresOn = getExpiresOnFromSeconds(authConfig.jwtExpirationInSeconds);
|
|
8
|
+
return {
|
|
9
|
+
accessToken,
|
|
10
|
+
refreshToken: activeRefreshToken.token,
|
|
11
|
+
expiresOn: accessTokenExpiresOn,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function generateDeviceId(): string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function generateRefreshToken(): string;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { IUserContext } from "@loomcore/common/models";
|
|
2
|
+
import type { IDatabase } from "../../databases/models/index.js";
|
|
3
|
+
import type { IRefreshToken } from "../../models/refresh-token.model.js";
|
|
4
|
+
export declare function getActiveRefreshToken(database: IDatabase, userContext: IUserContext, refreshToken: string, deviceId: string): Promise<IRefreshToken | null>;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { RefreshTokenService } from "../../services/refresh-token.service.js";
|
|
2
|
+
export async function getActiveRefreshToken(database, userContext, refreshToken, deviceId) {
|
|
3
|
+
const refreshTokenService = new RefreshTokenService(database);
|
|
4
|
+
const refreshTokenResult = await refreshTokenService.findOne(userContext, {
|
|
5
|
+
filters: {
|
|
6
|
+
token: { eq: refreshToken },
|
|
7
|
+
deviceId: { eq: deviceId },
|
|
8
|
+
},
|
|
9
|
+
});
|
|
10
|
+
if (!refreshTokenResult) {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
const now = Date.now();
|
|
14
|
+
const isExpired = refreshTokenResult.expiresOn < now;
|
|
15
|
+
if (isExpired) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
return refreshTokenResult;
|
|
19
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { generateDeviceId } from "./generate-device-id.util.js";
|
|
2
|
+
import { getAuthConfig } from "./get-auth-config.util.js";
|
|
3
|
+
import { getDeviceIdFromCookie } from "./get-device-id-from-cookie.util.js";
|
|
4
|
+
export function getAndSetDeviceIdCookie(req, res) {
|
|
5
|
+
let deviceId;
|
|
6
|
+
const deviceIdFromCookie = getDeviceIdFromCookie(req);
|
|
7
|
+
const isNewDeviceId = !deviceIdFromCookie;
|
|
8
|
+
if (deviceIdFromCookie) {
|
|
9
|
+
deviceId = deviceIdFromCookie;
|
|
10
|
+
}
|
|
11
|
+
else {
|
|
12
|
+
deviceId = generateDeviceId();
|
|
13
|
+
}
|
|
14
|
+
if (isNewDeviceId) {
|
|
15
|
+
const authConfig = getAuthConfig();
|
|
16
|
+
const cookieOptions = {
|
|
17
|
+
maxAge: authConfig.deviceIdCookieMaxAgeInDays * 24 * 60 * 60 * 1000,
|
|
18
|
+
httpOnly: true,
|
|
19
|
+
};
|
|
20
|
+
res.cookie("deviceId", deviceId, cookieOptions);
|
|
21
|
+
}
|
|
22
|
+
return deviceId;
|
|
23
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function getExpiresOnFromDays(expiresInDays: number): number;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function getExpiresOnFromMinutes(expiresInMinutes: number): number;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function getExpiresOnFromSeconds(expiresInSeconds: number): number;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export * from "./attempt-login.util.js";
|
|
2
|
+
export * from "./change-password.util.js";
|
|
3
|
+
export * from "./create-new-refresh-token.util.js";
|
|
4
|
+
export * from "./create-new-tokens.util.js";
|
|
5
|
+
export * from "./generate-device-id.util.js";
|
|
6
|
+
export * from "./generate-refresh-token.util.js";
|
|
7
|
+
export * from "./get-active-refresh-token.util.js";
|
|
8
|
+
export * from "./get-and-set-device-id-cookie.util.js";
|
|
9
|
+
export * from "./get-auth-config.util.js";
|
|
10
|
+
export * from "./get-device-id-from-cookie.util.js";
|
|
11
|
+
export * from "./get-expires-on-from-days.util.js";
|
|
12
|
+
export * from "./get-expires-on-from-minutes.util.js";
|
|
13
|
+
export * from "./get-expires-on-from-seconds.util.js";
|
|
14
|
+
export * from "./log-user-in.util.js";
|
|
15
|
+
export * from "./request-token-using-refresh-token.util.js";
|
|
16
|
+
export * from "./reset-password.util.js";
|
|
17
|
+
export * from "./send-reset-password-email.util.js";
|
|
18
|
+
export * from "./update-last-logged-in.util.js";
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export * from "./attempt-login.util.js";
|
|
2
|
+
export * from "./change-password.util.js";
|
|
3
|
+
export * from "./create-new-refresh-token.util.js";
|
|
4
|
+
export * from "./create-new-tokens.util.js";
|
|
5
|
+
export * from "./generate-device-id.util.js";
|
|
6
|
+
export * from "./generate-refresh-token.util.js";
|
|
7
|
+
export * from "./get-active-refresh-token.util.js";
|
|
8
|
+
export * from "./get-and-set-device-id-cookie.util.js";
|
|
9
|
+
export * from "./get-auth-config.util.js";
|
|
10
|
+
export * from "./get-device-id-from-cookie.util.js";
|
|
11
|
+
export * from "./get-expires-on-from-days.util.js";
|
|
12
|
+
export * from "./get-expires-on-from-minutes.util.js";
|
|
13
|
+
export * from "./get-expires-on-from-seconds.util.js";
|
|
14
|
+
export * from "./log-user-in.util.js";
|
|
15
|
+
export * from "./request-token-using-refresh-token.util.js";
|
|
16
|
+
export * from "./reset-password.util.js";
|
|
17
|
+
export * from "./send-reset-password-email.util.js";
|
|
18
|
+
export * from "./update-last-logged-in.util.js";
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { generateJwt } from "../jwt.utils.js";
|
|
2
|
+
import { createNewRefreshToken } from "./create-new-refresh-token.util.js";
|
|
3
|
+
import { getAuthConfig } from "./get-auth-config.util.js";
|
|
4
|
+
import { getExpiresOnFromSeconds } from "./get-expires-on-from-seconds.util.js";
|
|
5
|
+
import { updateLastLoggedIn } from "./update-last-logged-in.util.js";
|
|
6
|
+
export async function logUserIn(database, userContext, deviceId) {
|
|
7
|
+
const authConfig = getAuthConfig();
|
|
8
|
+
const accessToken = generateJwt(userContext);
|
|
9
|
+
const refreshTokenObject = await createNewRefreshToken(database, userContext.user._id, deviceId, userContext.organization?._id);
|
|
10
|
+
const accessTokenExpiresOn = getExpiresOnFromSeconds(authConfig.jwtExpirationInSeconds);
|
|
11
|
+
const tokenResponse = {
|
|
12
|
+
accessToken,
|
|
13
|
+
refreshToken: refreshTokenObject.token,
|
|
14
|
+
expiresOn: accessTokenExpiresOn,
|
|
15
|
+
};
|
|
16
|
+
updateLastLoggedIn(database, userContext.user._id).catch((err) => console.log(`Error updating lastLoggedIn: ${err}`));
|
|
17
|
+
return tokenResponse;
|
|
18
|
+
}
|