@loomcore/api 0.2.7 → 0.2.9

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 (90) hide show
  1. package/dist/__tests__/common-test.utils.d.ts +8 -6
  2. package/dist/__tests__/common-test.utils.js +61 -37
  3. package/dist/__tests__/test-express-app.d.ts +20 -19
  4. package/dist/__tests__/test-express-app.js +72 -68
  5. package/dist/__tests__/test-objects.d.ts +5 -0
  6. package/dist/__tests__/test-objects.js +16 -0
  7. package/dist/config/base-api-config.d.ts +2 -2
  8. package/dist/config/base-api-config.js +2 -2
  9. package/dist/controllers/auth.controller.d.ts +9 -8
  10. package/dist/controllers/auth.controller.js +76 -29
  11. package/dist/controllers/authorizations.controller.d.ts +3 -3
  12. package/dist/controllers/authorizations.controller.js +4 -4
  13. package/dist/controllers/roles.controller.d.ts +1 -1
  14. package/dist/controllers/roles.controller.js +3 -3
  15. package/dist/controllers/users.controller.d.ts +5 -6
  16. package/dist/controllers/users.controller.js +4 -15
  17. package/dist/databases/postgres/commands/postgres-create.command.d.ts +2 -2
  18. package/dist/databases/postgres/commands/postgres-create.command.js +5 -5
  19. package/dist/databases/postgres/migrations/__tests__/test-migration-helper.js +2 -1
  20. package/dist/databases/postgres/migrations/postgres-initial-schema.js +7 -5
  21. package/dist/databases/postgres/postgres.database.d.ts +2 -2
  22. package/dist/databases/postgres/postgres.database.js +6 -8
  23. package/dist/middleware/index.d.ts +4 -4
  24. package/dist/middleware/index.js +4 -4
  25. package/dist/middleware/is-authorized.d.ts +2 -2
  26. package/dist/middleware/is-authorized.js +13 -11
  27. package/dist/models/app-config.interface.d.ts +1 -1
  28. package/dist/models/base-api-config.interface.d.ts +5 -5
  29. package/dist/models/initial-database-config.interface.d.ts +4 -3
  30. package/dist/models/refresh-token.model.d.ts +1 -1
  31. package/dist/models/refresh-token.model.js +3 -4
  32. package/dist/services/generic-api-service/generic-api-service.interface.d.ts +6 -6
  33. package/dist/services/generic-api-service/generic-api.service.d.ts +8 -8
  34. package/dist/services/generic-api-service/generic-api.service.js +16 -16
  35. package/dist/services/index.d.ts +11 -12
  36. package/dist/services/index.js +11 -12
  37. package/dist/services/multi-tenant-api.service.js +6 -2
  38. package/dist/services/password-reset-token.service.d.ts +2 -3
  39. package/dist/services/password-reset-token.service.js +4 -9
  40. package/dist/services/refresh-token.service.d.ts +6 -0
  41. package/dist/services/refresh-token.service.js +7 -0
  42. package/dist/services/user.service.js +4 -4
  43. package/dist/utils/auth/attempt-login.util.d.ts +3 -0
  44. package/dist/utils/auth/attempt-login.util.js +37 -0
  45. package/dist/utils/auth/change-password.util.d.ts +4 -0
  46. package/dist/utils/auth/change-password.util.js +14 -0
  47. package/dist/utils/auth/create-new-refresh-token.util.d.ts +4 -0
  48. package/dist/utils/auth/create-new-refresh-token.util.js +29 -0
  49. package/dist/utils/auth/create-new-tokens.util.d.ts +3 -0
  50. package/dist/utils/auth/create-new-tokens.util.js +13 -0
  51. package/dist/utils/auth/generate-device-id.util.d.ts +1 -0
  52. package/dist/utils/auth/generate-device-id.util.js +4 -0
  53. package/dist/utils/auth/generate-refresh-token.util.d.ts +1 -0
  54. package/dist/utils/auth/generate-refresh-token.util.js +4 -0
  55. package/dist/utils/auth/get-active-refresh-token.util.d.ts +4 -0
  56. package/dist/utils/auth/get-active-refresh-token.util.js +19 -0
  57. package/dist/utils/auth/get-and-set-device-id-cookie.util.d.ts +2 -0
  58. package/dist/utils/auth/get-and-set-device-id-cookie.util.js +23 -0
  59. package/dist/utils/auth/get-auth-config.util.d.ts +2 -0
  60. package/dist/utils/auth/get-auth-config.util.js +8 -0
  61. package/dist/utils/auth/get-device-id-from-cookie.util.d.ts +2 -0
  62. package/dist/utils/auth/get-device-id-from-cookie.util.js +3 -0
  63. package/dist/utils/auth/get-expires-on-from-days.util.d.ts +1 -0
  64. package/dist/utils/auth/get-expires-on-from-days.util.js +3 -0
  65. package/dist/utils/auth/get-expires-on-from-minutes.util.d.ts +1 -0
  66. package/dist/utils/auth/get-expires-on-from-minutes.util.js +3 -0
  67. package/dist/utils/auth/get-expires-on-from-seconds.util.d.ts +1 -0
  68. package/dist/utils/auth/get-expires-on-from-seconds.util.js +3 -0
  69. package/dist/utils/auth/index.d.ts +18 -0
  70. package/dist/utils/auth/index.js +18 -0
  71. package/dist/utils/auth/log-user-in.util.d.ts +3 -0
  72. package/dist/utils/auth/log-user-in.util.js +18 -0
  73. package/dist/utils/auth/request-token-using-refresh-token.util.d.ts +3 -0
  74. package/dist/utils/auth/request-token-using-refresh-token.util.js +9 -0
  75. package/dist/utils/auth/reset-password.util.d.ts +4 -0
  76. package/dist/utils/auth/reset-password.util.js +39 -0
  77. package/dist/utils/auth/send-reset-password-email.util.d.ts +3 -0
  78. package/dist/utils/auth/send-reset-password-email.util.js +23 -0
  79. package/dist/utils/auth/update-last-logged-in.util.d.ts +3 -0
  80. package/dist/utils/auth/update-last-logged-in.util.js +15 -0
  81. package/dist/utils/index.d.ts +8 -6
  82. package/dist/utils/index.js +8 -6
  83. package/dist/utils/jwt.utils.d.ts +2 -0
  84. package/dist/utils/jwt.utils.js +12 -0
  85. package/dist/utils/password.utils.js +8 -8
  86. package/package.json +3 -3
  87. package/dist/services/auth.service.d.ts +0 -49
  88. package/dist/services/auth.service.js +0 -277
  89. package/dist/services/jwt.service.d.ts +0 -4
  90. package/dist/services/jwt.service.js +0 -18
@@ -1,10 +1,10 @@
1
- import _ from 'lodash';
2
- import { DefaultQueryOptions } from '@loomcore/common/models';
3
- import { entityUtils } from '@loomcore/common/utils';
4
- import { stripSenderProvidedSystemProperties } from '../utils/strip-sender-provided-system-properties.util.js';
5
- import { auditForCreate } from '../utils/audit-for-create.util.js';
6
- import { auditForUpdate } from '../utils/audit-for-update.util.js';
7
- import { IdNotFoundError, ServerError } from '../../errors/index.js';
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, []);
@@ -1,12 +1,11 @@
1
- export * from './auth.service.js';
2
- export * from './email.service.js';
3
- export * from './generic-api-service/generic-api.service.js';
4
- export * from './generic-api-service/generic-api-service.interface.js';
5
- export * from './generic-query-service/generic-query.service.js';
6
- export * from './generic-query-service/generic-query-service.interface.js';
7
- export * from './jwt.service.js';
8
- export * from './multi-tenant-api.service.js';
9
- export * from './organization.service.js';
10
- export * from './password-reset-token.service.js';
11
- export * from './tenant-query-decorator.js';
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,12 +1,11 @@
1
- export * from './auth.service.js';
2
- export * from './email.service.js';
3
- export * from './generic-api-service/generic-api.service.js';
4
- export * from './generic-api-service/generic-api-service.interface.js';
5
- export * from './generic-query-service/generic-query.service.js';
6
- export * from './generic-query-service/generic-query-service.interface.js';
7
- export * from './jwt.service.js';
8
- export * from './multi-tenant-api.service.js';
9
- export * from './organization.service.js';
10
- export * from './password-reset-token.service.js';
11
- export * from './tenant-query-decorator.js';
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 || !userContext.organization?._id) {
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 || !userContext.organization?._id) {
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 { getSystemUserContext, PasswordResetTokenSpec, } from "@loomcore/common/models";
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(getSystemUserContext(), {
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(getSystemUserContext(), passwordResetToken);
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("Cannot full update a user. Either use PATCH or /auth/change-password to update password.");
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 (!isCreate) {
17
- const { password, ...cleanedEntity } = preparedEntity;
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,3 @@
1
+ import { type ILoginResponse, type IOrganization } from "@loomcore/common/models";
2
+ import type { IDatabase } from "../../databases/models/index.js";
3
+ export declare function attemptLogin(database: IDatabase, email: string, password: string, deviceId: string, organization: IOrganization | null): Promise<ILoginResponse | null>;
@@ -0,0 +1,37 @@
1
+ import { EmptyUserContext, } from "@loomcore/common/models";
2
+ import { BadRequestError } from "../../errors/index.js";
3
+ import { UserService } from "../../services/user.service.js";
4
+ import { getUserContextAuthorizations } from "../../services/utils/getUserContextAuthorizations.util.js";
5
+ import { passwordUtils } from "../password.utils.js";
6
+ import { logUserIn } from "./log-user-in.util.js";
7
+ export async function attemptLogin(database, email, password, deviceId, organization) {
8
+ const lowerCaseEmail = email.toLowerCase();
9
+ const userContext = {
10
+ ...EmptyUserContext,
11
+ organization: organization ?? undefined,
12
+ };
13
+ const userService = new UserService(database);
14
+ const user = await userService.findOne(userContext, {
15
+ filters: {
16
+ email: { eq: lowerCaseEmail },
17
+ },
18
+ });
19
+ if (!user) {
20
+ throw new BadRequestError("Invalid Credentials");
21
+ }
22
+ const passwordsMatch = await passwordUtils.comparePasswords(user.password, password);
23
+ if (!passwordsMatch) {
24
+ throw new BadRequestError("Invalid Credentials");
25
+ }
26
+ const authorizations = await getUserContextAuthorizations(database, user);
27
+ const authenticatedUserContext = {
28
+ user: user,
29
+ organization: organization ?? undefined,
30
+ authorizations: authorizations,
31
+ };
32
+ const tokens = await logUserIn(database, authenticatedUserContext, deviceId);
33
+ return {
34
+ tokens,
35
+ userContext: authenticatedUserContext,
36
+ };
37
+ }
@@ -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,4 @@
1
+ import crypto from "node:crypto";
2
+ export function generateDeviceId() {
3
+ return crypto.randomBytes(40).toString("hex");
4
+ }
@@ -0,0 +1 @@
1
+ export declare function generateRefreshToken(): string;
@@ -0,0 +1,4 @@
1
+ import crypto from "node:crypto";
2
+ export function generateRefreshToken() {
3
+ return crypto.randomBytes(40).toString("hex");
4
+ }
@@ -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,2 @@
1
+ import type { Request, Response } from "express";
2
+ export declare function getAndSetDeviceIdCookie(req: Request, res: Response): string;
@@ -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,2 @@
1
+ import type { IAuthConfig } from "../../models/auth-config.interface.js";
2
+ export declare function getAuthConfig(): IAuthConfig;
@@ -0,0 +1,8 @@
1
+ import { config } from "../../config/index.js";
2
+ import { ServerError } from "../../errors/index.js";
3
+ export function getAuthConfig() {
4
+ if (!config.auth) {
5
+ throw new ServerError("Auth configuration is not set");
6
+ }
7
+ return config.auth;
8
+ }
@@ -0,0 +1,2 @@
1
+ import type { Request } from "express";
2
+ export declare function getDeviceIdFromCookie(req: Request): any;
@@ -0,0 +1,3 @@
1
+ export function getDeviceIdFromCookie(req) {
2
+ return req.cookies.deviceId;
3
+ }
@@ -0,0 +1 @@
1
+ export declare function getExpiresOnFromDays(expiresInDays: number): number;
@@ -0,0 +1,3 @@
1
+ export function getExpiresOnFromDays(expiresInDays) {
2
+ return Date.now() + expiresInDays * 24 * 60 * 60 * 1000;
3
+ }
@@ -0,0 +1 @@
1
+ export declare function getExpiresOnFromMinutes(expiresInMinutes: number): number;
@@ -0,0 +1,3 @@
1
+ export function getExpiresOnFromMinutes(expiresInMinutes) {
2
+ return Date.now() + expiresInMinutes * 60 * 1000;
3
+ }
@@ -0,0 +1 @@
1
+ export declare function getExpiresOnFromSeconds(expiresInSeconds: number): number;
@@ -0,0 +1,3 @@
1
+ export function getExpiresOnFromSeconds(expiresInSeconds) {
2
+ return Date.now() + expiresInSeconds * 1000;
3
+ }
@@ -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,3 @@
1
+ import type { ITokenResponse, IUserContext } from "@loomcore/common/models";
2
+ import type { IDatabase } from "../../databases/models/index.js";
3
+ export declare function logUserIn(database: IDatabase, userContext: IUserContext, deviceId: string): Promise<ITokenResponse>;
@@ -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
+ }
@@ -0,0 +1,3 @@
1
+ import type { ITokenResponse, IUserContext } from "@loomcore/common/models";
2
+ import type { IDatabase } from "../../databases/models/index.js";
3
+ export declare function requestTokenUsingRefreshToken(database: IDatabase, userContext: IUserContext, refreshToken: string, deviceId: string): Promise<ITokenResponse | null>;
@@ -0,0 +1,9 @@
1
+ import { createNewTokens } from "./create-new-tokens.util.js";
2
+ import { getActiveRefreshToken } from "./get-active-refresh-token.util.js";
3
+ export async function requestTokenUsingRefreshToken(database, userContext, refreshToken, deviceId) {
4
+ const activeRefreshToken = await getActiveRefreshToken(database, userContext, refreshToken, deviceId);
5
+ if (activeRefreshToken) {
6
+ return createNewTokens(userContext, activeRefreshToken);
7
+ }
8
+ return null;
9
+ }
@@ -0,0 +1,4 @@
1
+ import { type IOrganization } 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 resetPassword(database: IDatabase, email: string, passwordResetToken: string, password: string, organization: IOrganization | null): Promise<UpdateResult>;