@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.
Files changed (77) hide show
  1. package/dist/__tests__/common-test.utils.d.ts +5 -5
  2. package/dist/__tests__/common-test.utils.js +24 -21
  3. package/dist/controllers/auth.controller.d.ts +9 -7
  4. package/dist/controllers/auth.controller.js +69 -27
  5. package/dist/controllers/authorizations.controller.d.ts +3 -3
  6. package/dist/controllers/authorizations.controller.js +4 -4
  7. package/dist/controllers/roles.controller.d.ts +1 -1
  8. package/dist/controllers/roles.controller.js +3 -3
  9. package/dist/controllers/users.controller.d.ts +5 -6
  10. package/dist/controllers/users.controller.js +4 -15
  11. package/dist/databases/postgres/commands/postgres-create.command.d.ts +2 -2
  12. package/dist/databases/postgres/commands/postgres-create.command.js +5 -5
  13. package/dist/databases/postgres/postgres.database.d.ts +2 -2
  14. package/dist/databases/postgres/postgres.database.js +6 -8
  15. package/dist/middleware/is-authorized.d.ts +2 -2
  16. package/dist/middleware/is-authorized.js +13 -11
  17. package/dist/models/refresh-token.model.d.ts +1 -1
  18. package/dist/models/refresh-token.model.js +3 -4
  19. package/dist/services/generic-api-service/generic-api-service.interface.d.ts +6 -6
  20. package/dist/services/generic-api-service/generic-api.service.d.ts +8 -8
  21. package/dist/services/generic-api-service/generic-api.service.js +16 -16
  22. package/dist/services/index.d.ts +11 -12
  23. package/dist/services/index.js +11 -12
  24. package/dist/services/multi-tenant-api.service.js +6 -2
  25. package/dist/services/password-reset-token.service.d.ts +2 -3
  26. package/dist/services/password-reset-token.service.js +4 -9
  27. package/dist/services/refresh-token.service.d.ts +6 -0
  28. package/dist/services/refresh-token.service.js +7 -0
  29. package/dist/services/user.service.js +4 -4
  30. package/dist/utils/auth/attempt-login.util.d.ts +4 -0
  31. package/dist/utils/auth/attempt-login.util.js +47 -0
  32. package/dist/utils/auth/change-password.util.d.ts +4 -0
  33. package/dist/utils/auth/change-password.util.js +14 -0
  34. package/dist/utils/auth/create-new-refresh-token.util.d.ts +4 -0
  35. package/dist/utils/auth/create-new-refresh-token.util.js +29 -0
  36. package/dist/utils/auth/create-new-tokens.util.d.ts +3 -0
  37. package/dist/utils/auth/create-new-tokens.util.js +13 -0
  38. package/dist/utils/auth/generate-device-id.util.d.ts +1 -0
  39. package/dist/utils/auth/generate-device-id.util.js +4 -0
  40. package/dist/utils/auth/generate-refresh-token.util.d.ts +1 -0
  41. package/dist/utils/auth/generate-refresh-token.util.js +4 -0
  42. package/dist/utils/auth/get-active-refresh-token.util.d.ts +4 -0
  43. package/dist/utils/auth/get-active-refresh-token.util.js +19 -0
  44. package/dist/utils/auth/get-and-set-device-id-cookie.util.d.ts +2 -0
  45. package/dist/utils/auth/get-and-set-device-id-cookie.util.js +23 -0
  46. package/dist/utils/auth/get-auth-config.util.d.ts +2 -0
  47. package/dist/utils/auth/get-auth-config.util.js +8 -0
  48. package/dist/utils/auth/get-device-id-from-cookie.util.d.ts +2 -0
  49. package/dist/utils/auth/get-device-id-from-cookie.util.js +3 -0
  50. package/dist/utils/auth/get-expires-on-from-days.util.d.ts +1 -0
  51. package/dist/utils/auth/get-expires-on-from-days.util.js +3 -0
  52. package/dist/utils/auth/get-expires-on-from-minutes.util.d.ts +1 -0
  53. package/dist/utils/auth/get-expires-on-from-minutes.util.js +3 -0
  54. package/dist/utils/auth/get-expires-on-from-seconds.util.d.ts +1 -0
  55. package/dist/utils/auth/get-expires-on-from-seconds.util.js +3 -0
  56. package/dist/utils/auth/index.d.ts +18 -0
  57. package/dist/utils/auth/index.js +18 -0
  58. package/dist/utils/auth/log-user-in.util.d.ts +3 -0
  59. package/dist/utils/auth/log-user-in.util.js +18 -0
  60. package/dist/utils/auth/request-token-using-refresh-token.util.d.ts +3 -0
  61. package/dist/utils/auth/request-token-using-refresh-token.util.js +9 -0
  62. package/dist/utils/auth/reset-password.util.d.ts +4 -0
  63. package/dist/utils/auth/reset-password.util.js +50 -0
  64. package/dist/utils/auth/send-reset-password-email.util.d.ts +3 -0
  65. package/dist/utils/auth/send-reset-password-email.util.js +30 -0
  66. package/dist/utils/auth/update-last-logged-in.util.d.ts +3 -0
  67. package/dist/utils/auth/update-last-logged-in.util.js +15 -0
  68. package/dist/utils/index.d.ts +8 -6
  69. package/dist/utils/index.js +8 -6
  70. package/dist/utils/jwt.utils.d.ts +2 -0
  71. package/dist/utils/jwt.utils.js +12 -0
  72. package/dist/utils/password.utils.js +8 -8
  73. package/package.json +3 -3
  74. package/dist/services/auth.service.d.ts +0 -49
  75. package/dist/services/auth.service.js +0 -277
  76. package/dist/services/jwt.service.d.ts +0 -4
  77. package/dist/services/jwt.service.js +0 -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 { AppIdType } from "@loomcore/common/types";
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, organizationId?: AppIdType): Promise<UpdateResult>;
@@ -0,0 +1,50 @@
1
+ import { EmptyUserContext, passwordValidator, UserSpec, } from "@loomcore/common/models";
2
+ import { entityUtils } from "@loomcore/common/utils";
3
+ import { config } from "../../config/base-api-config.js";
4
+ import { BadRequestError, ServerError } from "../../errors/index.js";
5
+ import { OrganizationService } from "../../services/organization.service.js";
6
+ import { PasswordResetTokenService } from "../../services/password-reset-token.service.js";
7
+ import { UserService } from "../../services/user.service.js";
8
+ import { changePassword } from "./change-password.util.js";
9
+ export async function resetPassword(database, email, passwordResetToken, password, organizationId) {
10
+ const validationErrors = entityUtils.validate(UserSpec, { password: password }, true, passwordValidator);
11
+ entityUtils.handleValidationResult(validationErrors, "AuthService.resetPassword");
12
+ if (config.app.isMultiTenant && !organizationId) {
13
+ throw new BadRequestError("Missing required fields: organizationId is required.");
14
+ }
15
+ const lowerCaseEmail = email.toLowerCase();
16
+ const organizationService = new OrganizationService(database);
17
+ const passwordResetTokenService = new PasswordResetTokenService(database);
18
+ const userService = new UserService(database);
19
+ const organization = organizationId
20
+ ? await organizationService.findOne(EmptyUserContext, {
21
+ filters: { _id: { eq: organizationId } },
22
+ })
23
+ : null;
24
+ const userContext = {
25
+ ...EmptyUserContext,
26
+ organization: organization ?? undefined,
27
+ };
28
+ const retrievedPasswordResetToken = await passwordResetTokenService.findOne(userContext, {
29
+ filters: { email: { eq: lowerCaseEmail } },
30
+ });
31
+ if (!retrievedPasswordResetToken) {
32
+ throw new ServerError(`Unable to retrieve password reset token for email: ${lowerCaseEmail}`);
33
+ }
34
+ if (retrievedPasswordResetToken.token !== passwordResetToken ||
35
+ retrievedPasswordResetToken.expiresOn < Date.now()) {
36
+ throw new BadRequestError("Invalid password reset token");
37
+ }
38
+ const user = await userService.findOne(userContext, {
39
+ filters: { email: { eq: lowerCaseEmail } },
40
+ });
41
+ if (!user) {
42
+ throw new ServerError(`Unable to retrieve user for email: ${lowerCaseEmail}`);
43
+ }
44
+ userContext.user = user;
45
+ const result = await changePassword(database, userContext, password);
46
+ console.log(`password changed using forgot-password for email: ${lowerCaseEmail}`);
47
+ await passwordResetTokenService.deleteById(userContext, retrievedPasswordResetToken._id);
48
+ console.log(`passwordResetToken deleted for email: ${lowerCaseEmail}`);
49
+ return result;
50
+ }
@@ -0,0 +1,3 @@
1
+ import type { AppIdType } from "@loomcore/common/types";
2
+ import type { IDatabase } from "../../databases/models/index.js";
3
+ export declare function sendResetPasswordEmail(database: IDatabase, emailAddress: string, clientBaseUrl: string, organizationId?: AppIdType): Promise<void>;
@@ -0,0 +1,30 @@
1
+ import { EmptyUserContext, } from "@loomcore/common/models";
2
+ import { ServerError } from "../../errors/index.js";
3
+ import { EmailService } from "../../services/email.service.js";
4
+ import { OrganizationService } from "../../services/organization.service.js";
5
+ import { PasswordResetTokenService } from "../../services/password-reset-token.service.js";
6
+ import { getAuthConfig } from "./get-auth-config.util.js";
7
+ import { getExpiresOnFromMinutes } from "./get-expires-on-from-minutes.util.js";
8
+ export async function sendResetPasswordEmail(database, emailAddress, clientBaseUrl, organizationId) {
9
+ const authConfig = getAuthConfig();
10
+ const organizationService = new OrganizationService(database);
11
+ const passwordResetTokenService = new PasswordResetTokenService(database);
12
+ const emailService = new EmailService();
13
+ const organization = organizationId
14
+ ? await organizationService.findOne(EmptyUserContext, {
15
+ filters: { _id: { eq: organizationId } },
16
+ })
17
+ : null;
18
+ const userContext = {
19
+ ...EmptyUserContext,
20
+ organization: organization ?? undefined,
21
+ };
22
+ const expiresOn = getExpiresOnFromMinutes(authConfig.passwordResetTokenExpirationInMinutes);
23
+ const passwordResetToken = await passwordResetTokenService.createPasswordResetToken(userContext, emailAddress, expiresOn);
24
+ if (!passwordResetToken) {
25
+ throw new ServerError(`Failed to create password reset token for email: ${emailAddress}`);
26
+ }
27
+ const urlEncodedEmail = encodeURIComponent(emailAddress);
28
+ const resetPasswordLink = `${clientBaseUrl}/reset-password/${passwordResetToken.token}/${urlEncodedEmail}`;
29
+ await emailService.sendResetPasswordEmail(emailAddress, resetPasswordLink);
30
+ }
@@ -0,0 +1,3 @@
1
+ import type { AppIdType } from "@loomcore/common/types";
2
+ import type { IDatabase } from "../../databases/models/index.js";
3
+ export declare function updateLastLoggedIn(database: IDatabase, userId: AppIdType): Promise<void>;
@@ -0,0 +1,15 @@
1
+ import { getSystemUserContext } from "@loomcore/common/models";
2
+ import moment from "moment";
3
+ import { UserService } from "../../services/user.service.js";
4
+ export async function updateLastLoggedIn(database, userId) {
5
+ try {
6
+ const updates = {
7
+ _lastLoggedIn: moment().utc().toDate(),
8
+ };
9
+ const userService = new UserService(database);
10
+ await userService.update(getSystemUserContext(), { filters: { _id: { eq: userId } } }, updates);
11
+ }
12
+ catch (error) {
13
+ console.error(`Error updating lastLoggedIn: ${error}`);
14
+ }
15
+ }
@@ -1,6 +1,8 @@
1
- export * from './address.utils.js';
2
- export * from './api.utils.js';
3
- export * from './conversion.utils.js';
4
- export * from './express.utils.js';
5
- export * from './password.utils.js';
6
- export * from './string.utils.js';
1
+ export * from "./address.utils.js";
2
+ export * from "./api.utils.js";
3
+ export * from "./auth/index.js";
4
+ export * from "./conversion.utils.js";
5
+ export * from "./express.utils.js";
6
+ export * from "./jwt.utils.js";
7
+ export * from "./password.utils.js";
8
+ export * from "./string.utils.js";
@@ -1,6 +1,8 @@
1
- export * from './address.utils.js';
2
- export * from './api.utils.js';
3
- export * from './conversion.utils.js';
4
- export * from './express.utils.js';
5
- export * from './password.utils.js';
6
- export * from './string.utils.js';
1
+ export * from "./address.utils.js";
2
+ export * from "./api.utils.js";
3
+ export * from "./auth/index.js";
4
+ export * from "./conversion.utils.js";
5
+ export * from "./express.utils.js";
6
+ export * from "./jwt.utils.js";
7
+ export * from "./password.utils.js";
8
+ export * from "./string.utils.js";
@@ -0,0 +1,2 @@
1
+ import type { IUserContext } from "@loomcore/common/models";
2
+ export declare function generateJwt(userContext: IUserContext): string;
@@ -0,0 +1,12 @@
1
+ import jwt from "jsonwebtoken";
2
+ import { getAuthConfig } from "./auth/get-auth-config.util.js";
3
+ export function generateJwt(userContext) {
4
+ const authConfig = getAuthConfig();
5
+ const jwtExpiryConfig = authConfig.jwtExpirationInSeconds;
6
+ const jwtExpirationInSeconds = typeof jwtExpiryConfig === "string"
7
+ ? Number.parseInt(jwtExpiryConfig, 10)
8
+ : jwtExpiryConfig;
9
+ return jwt.sign(userContext, authConfig.clientSecret, {
10
+ expiresIn: jwtExpirationInSeconds,
11
+ });
12
+ }
@@ -1,23 +1,23 @@
1
- import { scrypt, randomBytes } from 'node:crypto';
2
- import { promisify } from 'util';
1
+ import { randomBytes, scrypt } from "node:crypto";
2
+ import { promisify } from "util";
3
3
  const scryptAsync = promisify(scrypt);
4
4
  async function hashPassword(password) {
5
- const salt = randomBytes(8).toString('hex');
5
+ const salt = randomBytes(8).toString("hex");
6
6
  const buf = (await scryptAsync(password, salt, 64));
7
- return `${buf.toString('hex')}.${salt}`;
7
+ return `${buf.toString("hex")}.${salt}`;
8
8
  }
9
9
  async function comparePasswords(storedPassword, suppliedPassword) {
10
- if (!storedPassword || !storedPassword.includes('.')) {
10
+ if (!storedPassword?.includes(".")) {
11
11
  return false;
12
12
  }
13
- const [hashedPassword, salt] = storedPassword.split('.');
13
+ const [hashedPassword, salt] = storedPassword.split(".");
14
14
  if (!salt) {
15
15
  return false;
16
16
  }
17
17
  const buf = (await scryptAsync(suppliedPassword, salt, 64));
18
- return buf.toString('hex') === hashedPassword;
18
+ return buf.toString("hex") === hashedPassword;
19
19
  }
20
20
  export const passwordUtils = {
21
21
  hashPassword,
22
- comparePasswords
22
+ comparePasswords,
23
23
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loomcore/api",
3
- "version": "0.2.6",
3
+ "version": "0.2.8",
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": {
@@ -12,7 +12,7 @@
12
12
  "patch": "npm version patch",
13
13
  "push": "git push",
14
14
  "publishMe": "npm publish --access public",
15
- "pub": "npm-run-all -s add commit patch build push publishMe",
15
+ "pub": "npm-run-all -s patch build push publishMe",
16
16
  "update-lib-versions": "npx --yes npm-check-updates -u -f @loomcore/common",
17
17
  "install-updated-libs": "npm i @loomcore/common",
18
18
  "update-libs": "npm-run-all -s update-lib-versions install-updated-libs",
@@ -58,7 +58,7 @@
58
58
  "qs": "^6.15.2"
59
59
  },
60
60
  "peerDependencies": {
61
- "@loomcore/common": "^0.0.71",
61
+ "@loomcore/common": "^0.0.72",
62
62
  "@sinclair/typebox": "0.34.33",
63
63
  "cookie-parser": "^1.4.6",
64
64
  "cors": "^2.8.5",
@@ -1,49 +0,0 @@
1
- import { type ILoginResponse, type ITokenResponse, type IUser, type IUserContext } from "@loomcore/common/models";
2
- import type { AppIdType } from "@loomcore/common/types";
3
- import type { Request, Response } from "express";
4
- import type { IDatabase } from "../databases/models/index.js";
5
- import type { UpdateResult } from "../databases/models/update-result.js";
6
- import { type IRefreshToken } from "../models/refresh-token.model.js";
7
- import { MultiTenantApiService } from "./multi-tenant-api.service.js";
8
- export declare class AuthService extends MultiTenantApiService<IUser> {
9
- private refreshTokenService;
10
- private passwordResetTokenService;
11
- private emailService;
12
- private organizationService;
13
- private authConfig;
14
- constructor(database: IDatabase);
15
- attemptLogin(req: Request, res: Response, email: string, password: string): Promise<ILoginResponse | null>;
16
- logUserIn(userContext: IUserContext, deviceId: string): Promise<{
17
- tokens: {
18
- accessToken: string;
19
- refreshToken: string;
20
- expiresOn: number;
21
- };
22
- userContext: IUserContext;
23
- } | null>;
24
- getUserByEmail(email: string): Promise<IUser | null>;
25
- createUser(userContext: IUserContext, user: Partial<IUser>): Promise<IUser | null>;
26
- requestTokenUsingRefreshToken(refreshToken: string, deviceId: string): Promise<ITokenResponse | null>;
27
- changeLoggedInUsersPassword(userContext: IUserContext, body: any): Promise<UpdateResult>;
28
- changePassword(userContext: IUserContext, queryObject: any, password: string): Promise<UpdateResult>;
29
- createNewTokens(userContext: IUserContext, activeRefreshToken: IRefreshToken): Promise<{
30
- accessToken: string;
31
- refreshToken: string;
32
- expiresOn: number;
33
- }>;
34
- getActiveRefreshToken(refreshToken: string, deviceId: string): Promise<IRefreshToken | null>;
35
- createNewRefreshToken(userId: AppIdType, deviceId: string, orgId?: AppIdType): Promise<IRefreshToken | null>;
36
- sendResetPasswordEmail(emailAddress: string, clientBaseUrl: string): Promise<void>;
37
- resetPassword(email: string, passwordResetToken: string, password: string): Promise<UpdateResult>;
38
- deleteRefreshTokensForDevice(deviceId: string): Promise<import("../databases/models/delete-result.js").DeleteResult>;
39
- generateJwt(userContext: IUserContext): string;
40
- generateRefreshToken(): string;
41
- generateDeviceId(): string;
42
- getAndSetDeviceIdCookie(req: Request, res: Response): string;
43
- getDeviceIdFromCookie(req: Request): any;
44
- getExpiresOnFromSeconds(expiresInSeconds: number): number;
45
- getExpiresOnFromMinutes(expiresInMinutes: number): number;
46
- getExpiresOnFromDays(expiresInDays: number): number;
47
- preProcessEntity(userContext: IUserContext, entity: Partial<IUser>, isCreate: boolean, allowId: boolean): Promise<Partial<IUser>>;
48
- private updateLastLoggedIn;
49
- }
@@ -1,277 +0,0 @@
1
- import crypto from "node:crypto";
2
- import { EmptyUserContext, getSystemUserContext, passwordValidator, UserSpec, } from "@loomcore/common/models";
3
- import { entityUtils } from "@loomcore/common/utils";
4
- import moment from "moment";
5
- import { config } from "../config/index.js";
6
- import { BadRequestError, ServerError } from "../errors/index.js";
7
- import { refreshTokenModelSpec, } from "../models/refresh-token.model.js";
8
- import { passwordUtils } from "../utils/index.js";
9
- import { EmailService, JwtService } from "./index.js";
10
- import { MultiTenantApiService } from "./multi-tenant-api.service.js";
11
- import { OrganizationService } from "./organization.service.js";
12
- import { PasswordResetTokenService } from "./password-reset-token.service.js";
13
- import { getUserContextAuthorizations } from "./utils/getUserContextAuthorizations.util.js";
14
- export class AuthService extends MultiTenantApiService {
15
- refreshTokenService;
16
- passwordResetTokenService;
17
- emailService;
18
- organizationService;
19
- authConfig;
20
- constructor(database) {
21
- super(database, "users", "user", UserSpec);
22
- this.refreshTokenService = new MultiTenantApiService(database, "refresh_tokens", "refresh_token", refreshTokenModelSpec);
23
- this.passwordResetTokenService = new PasswordResetTokenService(database);
24
- this.emailService = new EmailService();
25
- this.organizationService = new OrganizationService(database);
26
- if (!config.auth) {
27
- throw new ServerError("Auth configuration is not set");
28
- }
29
- this.authConfig = config.auth;
30
- }
31
- async attemptLogin(req, res, email, password) {
32
- const lowerCaseEmail = email.toLowerCase();
33
- const user = await this.getUserByEmail(lowerCaseEmail);
34
- const organization = await this.organizationService.findOne(EmptyUserContext, { filters: { _id: { eq: user?._orgId } } });
35
- if (!user) {
36
- throw new BadRequestError("Invalid Credentials");
37
- }
38
- const passwordsMatch = await passwordUtils.comparePasswords(user.password, password);
39
- if (!passwordsMatch) {
40
- throw new BadRequestError("Invalid Credentials");
41
- }
42
- const authorizations = await getUserContextAuthorizations(this.database, user);
43
- const userContext = {
44
- user: user,
45
- organization: organization ?? undefined,
46
- authorizations: authorizations,
47
- };
48
- const deviceId = this.getAndSetDeviceIdCookie(req, res);
49
- const loginResponse = await this.logUserIn(userContext, deviceId);
50
- return loginResponse;
51
- }
52
- async logUserIn(userContext, deviceId) {
53
- const payload = userContext;
54
- const accessToken = this.generateJwt(payload);
55
- const refreshTokenObject = await this.createNewRefreshToken(userContext.user._id, deviceId, userContext.organization?._id);
56
- const accessTokenExpiresOn = this.getExpiresOnFromSeconds(this.authConfig.jwtExpirationInSeconds);
57
- let loginResponse = null;
58
- if (refreshTokenObject) {
59
- const tokenResponse = {
60
- accessToken,
61
- refreshToken: refreshTokenObject.token,
62
- expiresOn: accessTokenExpiresOn,
63
- };
64
- this.updateLastLoggedIn(userContext.user._id).catch((err) => console.log(`Error updating lastLoggedIn: ${err}`));
65
- userContext.user = this.postProcessEntity(userContext, userContext.user);
66
- loginResponse = { tokens: tokenResponse, userContext };
67
- }
68
- return loginResponse;
69
- }
70
- async getUserByEmail(email) {
71
- const queryOptions = { filters: { email: { eq: email.toLowerCase() } } };
72
- const rawUser = await this.database.findOne(queryOptions, "users");
73
- if (!rawUser) {
74
- return null;
75
- }
76
- return this.database.postProcessEntity(rawUser, this.modelSpec.fullSchema);
77
- }
78
- async createUser(userContext, user) {
79
- if (userContext.user._id === "system") {
80
- if (config.app.isMultiTenant &&
81
- userContext.organization?._id !== user._orgId) {
82
- throw new BadRequestError("User is not authorized to create a user in this organization");
83
- }
84
- if (user._orgId) {
85
- const org = await this.organizationService.findOne(userContext, {
86
- filters: { _id: { eq: user._orgId } },
87
- });
88
- if (!org) {
89
- throw new BadRequestError("The specified organization does not exist");
90
- }
91
- }
92
- }
93
- if (user.email) {
94
- const existingUser = await this.getUserByEmail(user.email);
95
- if (existingUser) {
96
- throw new BadRequestError("A user with this email address already exists");
97
- }
98
- }
99
- return await this.create(userContext, user);
100
- }
101
- async requestTokenUsingRefreshToken(refreshToken, deviceId) {
102
- let tokens = null;
103
- const activeRefreshToken = await this.getActiveRefreshToken(refreshToken, deviceId);
104
- if (activeRefreshToken) {
105
- const systemUserContext = getSystemUserContext();
106
- const user = await this.getById(systemUserContext, activeRefreshToken.userId);
107
- const organization = await this.organizationService.findOne(EmptyUserContext, { filters: { _id: { eq: user?._orgId } } });
108
- const authorizations = await getUserContextAuthorizations(this.database, user);
109
- const userContext = {
110
- user: user,
111
- organization: organization ?? undefined,
112
- authorizations: authorizations,
113
- };
114
- tokens = await this.createNewTokens(userContext, activeRefreshToken);
115
- }
116
- return tokens;
117
- }
118
- async changeLoggedInUsersPassword(userContext, body) {
119
- const queryObject = { _id: userContext.user._id };
120
- const result = await this.changePassword(userContext, queryObject, body.password);
121
- return result;
122
- }
123
- async changePassword(userContext, queryObject, password) {
124
- const updates = {
125
- password: password,
126
- _lastPasswordChange: moment().utc().toDate(),
127
- };
128
- const updatedUsers = await super.update(userContext, queryObject, updates);
129
- const result = {
130
- success: true,
131
- count: updatedUsers.length,
132
- };
133
- return result;
134
- }
135
- async createNewTokens(userContext, activeRefreshToken) {
136
- const payload = userContext;
137
- const accessToken = this.generateJwt(payload);
138
- const accessTokenExpiresOn = this.getExpiresOnFromSeconds(this.authConfig.jwtExpirationInSeconds);
139
- const tokenResponse = {
140
- accessToken,
141
- refreshToken: activeRefreshToken.token,
142
- expiresOn: accessTokenExpiresOn,
143
- };
144
- return tokenResponse;
145
- }
146
- async getActiveRefreshToken(refreshToken, deviceId) {
147
- const refreshTokenResult = await this.refreshTokenService.findOne(getSystemUserContext(), { filters: { token: { eq: refreshToken }, deviceId: { eq: deviceId } } });
148
- let activeRefreshToken = null;
149
- if (refreshTokenResult) {
150
- const now = Date.now();
151
- const notExpired = refreshTokenResult.expiresOn > now;
152
- if (notExpired) {
153
- activeRefreshToken = refreshTokenResult;
154
- }
155
- }
156
- return activeRefreshToken;
157
- }
158
- async createNewRefreshToken(userId, deviceId, orgId) {
159
- const expiresOn = this.getExpiresOnFromDays(this.authConfig.refreshTokenExpirationInDays);
160
- const newRefreshToken = {
161
- _orgId: orgId,
162
- token: this.generateRefreshToken(),
163
- deviceId,
164
- userId,
165
- expiresOn: expiresOn,
166
- created: moment().utc().toDate(),
167
- createdBy: userId,
168
- };
169
- await this.deleteRefreshTokensForDevice(deviceId);
170
- const insertResult = await this.refreshTokenService.create(getSystemUserContext(), newRefreshToken);
171
- return insertResult;
172
- }
173
- async sendResetPasswordEmail(emailAddress, clientBaseUrl) {
174
- const expiresOn = this.getExpiresOnFromMinutes(this.authConfig.passwordResetTokenExpirationInMinutes);
175
- const passwordResetToken = await this.passwordResetTokenService.createPasswordResetToken(emailAddress, expiresOn);
176
- if (!passwordResetToken) {
177
- throw new ServerError(`Failed to create password reset token for email: ${emailAddress}`);
178
- }
179
- const urlEncodedEmail = encodeURIComponent(emailAddress);
180
- const resetPasswordLink = `${clientBaseUrl}/reset-password/${passwordResetToken.token}/${urlEncodedEmail}`;
181
- await this.emailService.sendResetPasswordEmail(emailAddress, resetPasswordLink);
182
- }
183
- async resetPassword(email, passwordResetToken, password) {
184
- const lowerCaseEmail = email.toLowerCase();
185
- const retrievedPasswordResetToken = await this.passwordResetTokenService.getByEmail(lowerCaseEmail);
186
- if (!retrievedPasswordResetToken) {
187
- throw new ServerError(`Unable to retrieve password reset token for email: ${lowerCaseEmail}`);
188
- }
189
- if (retrievedPasswordResetToken.token !== passwordResetToken ||
190
- retrievedPasswordResetToken.expiresOn < Date.now()) {
191
- throw new BadRequestError("Invalid password reset token");
192
- }
193
- const validationErrors = entityUtils.validate(UserSpec, { password: password }, true, passwordValidator);
194
- entityUtils.handleValidationResult(validationErrors, "AuthService.resetPassword");
195
- const result = await this.changePassword(getSystemUserContext(), { email: lowerCaseEmail }, password);
196
- console.log(`password changed using forgot-password for email: ${lowerCaseEmail}`);
197
- await this.passwordResetTokenService.deleteById(getSystemUserContext(), retrievedPasswordResetToken._id.toString());
198
- console.log(`passwordResetToken deleted for email: ${lowerCaseEmail}`);
199
- return result;
200
- }
201
- deleteRefreshTokensForDevice(deviceId) {
202
- return this.refreshTokenService.deleteMany(getSystemUserContext(), {
203
- filters: { deviceId: { eq: deviceId } },
204
- });
205
- }
206
- generateJwt(userContext) {
207
- const jwtExpiryConfig = this.authConfig.jwtExpirationInSeconds;
208
- const jwtExpirationInSeconds = typeof jwtExpiryConfig === "string"
209
- ? parseInt(jwtExpiryConfig)
210
- : jwtExpiryConfig;
211
- const accessToken = JwtService.sign(userContext, this.authConfig.clientSecret, {
212
- expiresIn: jwtExpirationInSeconds,
213
- });
214
- return accessToken;
215
- }
216
- generateRefreshToken() {
217
- return crypto.randomBytes(40).toString("hex");
218
- }
219
- generateDeviceId() {
220
- return crypto.randomBytes(40).toString("hex");
221
- }
222
- getAndSetDeviceIdCookie(req, res) {
223
- let isNewDeviceId = false;
224
- let deviceId = "";
225
- const deviceIdFromCookie = this.getDeviceIdFromCookie(req);
226
- if (deviceIdFromCookie) {
227
- deviceId = deviceIdFromCookie;
228
- }
229
- else {
230
- deviceId = this.generateDeviceId();
231
- isNewDeviceId = true;
232
- }
233
- if (isNewDeviceId) {
234
- const cookieOptions = {
235
- maxAge: this.authConfig.deviceIdCookieMaxAgeInDays * 24 * 60 * 60 * 1000,
236
- httpOnly: true,
237
- };
238
- res.cookie("deviceId", deviceId, cookieOptions);
239
- }
240
- return deviceId;
241
- }
242
- getDeviceIdFromCookie(req) {
243
- return req.cookies.deviceId;
244
- }
245
- getExpiresOnFromSeconds(expiresInSeconds) {
246
- return Date.now() + expiresInSeconds * 1000;
247
- }
248
- getExpiresOnFromMinutes(expiresInMinutes) {
249
- return Date.now() + expiresInMinutes * 60 * 1000;
250
- }
251
- getExpiresOnFromDays(expiresInDays) {
252
- return Date.now() + expiresInDays * 24 * 60 * 60 * 1000;
253
- }
254
- async preProcessEntity(userContext, entity, isCreate, allowId) {
255
- if (entity.email) {
256
- entity.email = entity.email?.toLowerCase();
257
- }
258
- if (entity.password) {
259
- const hash = await passwordUtils.hashPassword(entity.password);
260
- entity.password = hash;
261
- }
262
- const preparedEntity = await super.preProcessEntity(userContext, entity, isCreate, allowId);
263
- return preparedEntity;
264
- }
265
- async updateLastLoggedIn(userId) {
266
- try {
267
- const updates = {
268
- _lastLoggedIn: moment().utc().toDate(),
269
- };
270
- const systemUserContext = getSystemUserContext();
271
- await this.partialUpdateById(systemUserContext, userId, updates);
272
- }
273
- catch (error) {
274
- console.error(`Error updating lastLoggedIn: ${error}`);
275
- }
276
- }
277
- }
@@ -1,4 +0,0 @@
1
- export declare class JwtService {
2
- static sign(payload: any, secret: string, options: any): string;
3
- static verify(token: string, secret: string): any;
4
- }
@@ -1,18 +0,0 @@
1
- import jwt from "jsonwebtoken";
2
- export class JwtService {
3
- static sign(payload, secret, options) {
4
- return jwt.sign(payload, secret, options);
5
- }
6
- static verify(token, secret) {
7
- if (!secret) {
8
- throw new Error("JWT secret is required for verification");
9
- }
10
- try {
11
- const decoded = jwt.verify(token, secret);
12
- return decoded;
13
- }
14
- catch (error) {
15
- throw error;
16
- }
17
- }
18
- }