@loomcore/api 0.1.20 → 0.1.22

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.
@@ -31,11 +31,19 @@ export class AuthController {
31
31
  const body = req.body;
32
32
  const validationErrors = this.authService.validate(body);
33
33
  entityUtils.handleValidationResult(validationErrors, 'AuthController.registerUser');
34
+ if (!userContext) {
35
+ throw new BadRequestError('Missing required fields: userContext is required.');
36
+ }
34
37
  const user = await this.authService.createUser(userContext, body);
35
38
  apiUtils.apiResponse(res, 201, { data: user || undefined }, UserSpec, PublicUserSchema);
36
39
  }
37
40
  async requestTokenUsingRefreshToken(req, res, next) {
38
- let tokens = await this.authService.requestTokenUsingRefreshToken(req);
41
+ const refreshToken = req.query.refreshToken;
42
+ if (!refreshToken || typeof refreshToken !== 'string') {
43
+ throw new BadRequestError('Missing required fields: refreshToken is required.');
44
+ }
45
+ const deviceId = this.authService.getDeviceIdFromCookie(req);
46
+ const tokens = await this.authService.requestTokenUsingRefreshToken(refreshToken, deviceId);
39
47
  if (tokens) {
40
48
  apiUtils.apiResponse(res, 200, { data: tokens }, TokenResponseSpec);
41
49
  }
@@ -1,13 +1,14 @@
1
1
  import { Request, Response } from 'express';
2
2
  import { IUserContext, IUser, ITokenResponse, ILoginResponse } from '@loomcore/common/models';
3
- import { GenericApiService } from './generic-api-service/generic-api.service.js';
3
+ import { MultiTenantApiService } from './multi-tenant-api.service.js';
4
4
  import { UpdateResult } from '../databases/models/update-result.js';
5
5
  import { IRefreshToken } from '../models/refresh-token.model.js';
6
6
  import { IDatabase } from '../databases/models/index.js';
7
- export declare class AuthService extends GenericApiService<IUser> {
7
+ export declare class AuthService extends MultiTenantApiService<IUser> {
8
8
  private refreshTokenService;
9
9
  private passwordResetTokenService;
10
10
  private emailService;
11
+ private organizationService;
11
12
  constructor(database: IDatabase);
12
13
  attemptLogin(req: Request, res: Response, email: string, password: string): Promise<ILoginResponse | null>;
13
14
  logUserIn(userContext: IUserContext, deviceId: string): Promise<{
@@ -18,23 +19,22 @@ export declare class AuthService extends GenericApiService<IUser> {
18
19
  };
19
20
  userContext: IUserContext;
20
21
  } | null>;
21
- getUserById(id: string): Promise<IUser | null>;
22
22
  getUserByEmail(email: string): Promise<IUser | null>;
23
23
  createUser(userContext: IUserContext, user: Partial<IUser>): Promise<IUser | null>;
24
- requestTokenUsingRefreshToken(req: Request): Promise<ITokenResponse | null>;
24
+ requestTokenUsingRefreshToken(refreshToken: string, deviceId: string): Promise<ITokenResponse | null>;
25
25
  changeLoggedInUsersPassword(userContext: IUserContext, body: any): Promise<UpdateResult>;
26
26
  changePassword(userContext: IUserContext, queryObject: any, password: string): Promise<UpdateResult>;
27
- createNewTokens(userId: string, deviceId: string, refreshTokenExpiresOn: number): Promise<{
27
+ createNewTokens(userContext: IUserContext, activeRefreshToken: IRefreshToken): Promise<{
28
28
  accessToken: string;
29
- refreshToken: any;
29
+ refreshToken: string;
30
30
  expiresOn: number;
31
- } | null>;
32
- getActiveRefreshToken(userContext: IUserContext, refreshToken: string, deviceId: string): Promise<IRefreshToken | null>;
33
- createNewRefreshToken(userId: string, deviceId: string, existingExpiresOn?: number | null, orgId?: string): Promise<IRefreshToken | null>;
31
+ }>;
32
+ getActiveRefreshToken(refreshToken: string, deviceId: string): Promise<IRefreshToken | null>;
33
+ createNewRefreshToken(userId: string, deviceId: string, orgId?: string): Promise<IRefreshToken | null>;
34
34
  sendResetPasswordEmail(emailAddress: string): Promise<void>;
35
35
  resetPassword(email: string, passwordResetToken: string, password: string): Promise<UpdateResult>;
36
36
  deleteRefreshTokensForDevice(deviceId: string): Promise<import("../databases/models/delete-result.js").DeleteResult>;
37
- generateJwt(payload: any): string;
37
+ generateJwt(userContext: IUserContext): string;
38
38
  generateRefreshToken(): string;
39
39
  generateDeviceId(): string;
40
40
  getAndSetDeviceIdCookie(req: Request, res: Response): string;
@@ -5,19 +5,23 @@ import { entityUtils } from '@loomcore/common/utils';
5
5
  import { BadRequestError, ServerError } from '../errors/index.js';
6
6
  import { JwtService, EmailService } from './index.js';
7
7
  import { GenericApiService } from './generic-api-service/generic-api.service.js';
8
+ import { MultiTenantApiService } from './multi-tenant-api.service.js';
8
9
  import { PasswordResetTokenService } from './password-reset-token.service.js';
10
+ import { OrganizationService } from './organization.service.js';
9
11
  import { passwordUtils } from '../utils/index.js';
10
12
  import { config } from '../config/index.js';
11
13
  import { refreshTokenModelSpec } from '../models/refresh-token.model.js';
12
- export class AuthService extends GenericApiService {
14
+ export class AuthService extends MultiTenantApiService {
13
15
  refreshTokenService;
14
16
  passwordResetTokenService;
15
17
  emailService;
18
+ organizationService;
16
19
  constructor(database) {
17
20
  super(database, 'users', 'user', UserSpec);
18
21
  this.refreshTokenService = new GenericApiService(database, 'refreshTokens', 'refreshToken', refreshTokenModelSpec);
19
22
  this.passwordResetTokenService = new PasswordResetTokenService(database);
20
23
  this.emailService = new EmailService();
24
+ this.organizationService = new OrganizationService(database);
21
25
  }
22
26
  async attemptLogin(req, res, email, password) {
23
27
  const lowerCaseEmail = email.toLowerCase();
@@ -40,7 +44,7 @@ export class AuthService extends GenericApiService {
40
44
  async logUserIn(userContext, deviceId) {
41
45
  const payload = userContext;
42
46
  const accessToken = this.generateJwt(payload);
43
- const refreshTokenObject = await this.createNewRefreshToken(userContext.user._id, deviceId, null, userContext._orgId);
47
+ const refreshTokenObject = await this.createNewRefreshToken(userContext.user._id, deviceId, userContext._orgId);
44
48
  const accessTokenExpiresOn = this.getExpiresOnFromSeconds(config.auth.jwtExpirationInSeconds);
45
49
  let loginResponse = null;
46
50
  if (refreshTokenObject) {
@@ -56,31 +60,45 @@ export class AuthService extends GenericApiService {
56
60
  }
57
61
  return loginResponse;
58
62
  }
59
- async getUserById(id) {
60
- const user = await this.findOne(EmptyUserContext, { filters: { _id: { eq: id } } });
61
- return user;
62
- }
63
63
  async getUserByEmail(email) {
64
- const user = await this.findOne(EmptyUserContext, { filters: { email: { eq: email.toLowerCase() } } });
65
- return user;
64
+ const queryOptions = { filters: { email: { eq: email.toLowerCase() } } };
65
+ const rawUser = await this.database.findOne(queryOptions, 'users');
66
+ if (!rawUser) {
67
+ return null;
68
+ }
69
+ const userContext = {
70
+ _orgId: rawUser._orgId,
71
+ user: rawUser
72
+ };
73
+ return this.postprocessEntity(userContext, rawUser);
66
74
  }
67
75
  async createUser(userContext, user) {
76
+ if (user.email) {
77
+ const existingUser = await this.getUserByEmail(user.email);
78
+ if (existingUser) {
79
+ throw new BadRequestError('A user with this email address already exists');
80
+ }
81
+ }
82
+ if (user._orgId && userContext._orgId && userContext._orgId !== user._orgId && userContext.user?._id !== 'system') {
83
+ const org = await this.organizationService.findOne(userContext, { filters: { _id: { eq: user._orgId } } });
84
+ if (!org) {
85
+ throw new BadRequestError('The specified organization does not exist');
86
+ }
87
+ }
68
88
  const createdUser = await this.create(userContext, user);
69
89
  return createdUser;
70
90
  }
71
- async requestTokenUsingRefreshToken(req) {
72
- const refreshToken = req.query.refreshToken;
73
- const deviceId = this.getDeviceIdFromCookie(req);
91
+ async requestTokenUsingRefreshToken(refreshToken, deviceId) {
74
92
  let tokens = null;
75
- if (refreshToken && typeof refreshToken === 'string' && deviceId && typeof deviceId === 'string') {
76
- let userId = null;
77
- const activeRefreshToken = await this.getActiveRefreshToken(EmptyUserContext, refreshToken, deviceId);
78
- if (activeRefreshToken) {
79
- userId = activeRefreshToken.userId;
80
- if (userId) {
81
- tokens = await this.createNewTokens(userId, deviceId, activeRefreshToken.expiresOn);
82
- }
83
- }
93
+ const activeRefreshToken = await this.getActiveRefreshToken(refreshToken, deviceId);
94
+ if (activeRefreshToken) {
95
+ const systemUserContext = getSystemUserContext();
96
+ const user = await this.getById(systemUserContext, activeRefreshToken.userId);
97
+ const userContext = {
98
+ _orgId: user._orgId,
99
+ user: user
100
+ };
101
+ tokens = await this.createNewTokens(userContext, activeRefreshToken);
84
102
  }
85
103
  return tokens;
86
104
  }
@@ -98,31 +116,19 @@ export class AuthService extends GenericApiService {
98
116
  };
99
117
  return result;
100
118
  }
101
- async createNewTokens(userId, deviceId, refreshTokenExpiresOn) {
102
- let createdRefreshTokenObject = null;
103
- const user = await this.getUserById(userId);
104
- const newRefreshToken = await this.createNewRefreshToken(userId, deviceId, refreshTokenExpiresOn, user?._orgId);
105
- if (newRefreshToken) {
106
- createdRefreshTokenObject = newRefreshToken;
107
- }
108
- let tokenResponse = null;
109
- if (user && createdRefreshTokenObject) {
110
- const payload = {
111
- user: user,
112
- _orgId: user._orgId
113
- };
114
- const accessToken = this.generateJwt(payload);
115
- const accessTokenExpiresOn = this.getExpiresOnFromSeconds(config.auth.jwtExpirationInSeconds);
116
- tokenResponse = {
117
- accessToken,
118
- refreshToken: createdRefreshTokenObject.token,
119
- expiresOn: accessTokenExpiresOn
120
- };
121
- }
119
+ async createNewTokens(userContext, activeRefreshToken) {
120
+ const payload = userContext;
121
+ const accessToken = this.generateJwt(payload);
122
+ const accessTokenExpiresOn = this.getExpiresOnFromSeconds(config.auth.jwtExpirationInSeconds);
123
+ const tokenResponse = {
124
+ accessToken,
125
+ refreshToken: activeRefreshToken.token,
126
+ expiresOn: accessTokenExpiresOn
127
+ };
122
128
  return tokenResponse;
123
129
  }
124
- async getActiveRefreshToken(userContext, refreshToken, deviceId) {
125
- const refreshTokenResult = await this.refreshTokenService.findOne(userContext, { filters: { token: { eq: refreshToken }, deviceId: { eq: deviceId } } });
130
+ async getActiveRefreshToken(refreshToken, deviceId) {
131
+ const refreshTokenResult = await this.refreshTokenService.findOne(EmptyUserContext, { filters: { token: { eq: refreshToken }, deviceId: { eq: deviceId } } });
126
132
  let activeRefreshToken = null;
127
133
  if (refreshTokenResult) {
128
134
  const now = Date.now();
@@ -133,8 +139,8 @@ export class AuthService extends GenericApiService {
133
139
  }
134
140
  return activeRefreshToken;
135
141
  }
136
- async createNewRefreshToken(userId, deviceId, existingExpiresOn = null, orgId) {
137
- const expiresOn = existingExpiresOn ? existingExpiresOn : this.getExpiresOnFromDays(config.auth.refreshTokenExpirationInDays);
142
+ async createNewRefreshToken(userId, deviceId, orgId) {
143
+ const expiresOn = this.getExpiresOnFromDays(config.auth.refreshTokenExpirationInDays);
138
144
  const newRefreshToken = {
139
145
  _orgId: orgId,
140
146
  token: this.generateRefreshToken(),
@@ -181,13 +187,10 @@ export class AuthService extends GenericApiService {
181
187
  deleteRefreshTokensForDevice(deviceId) {
182
188
  return this.refreshTokenService.deleteMany(EmptyUserContext, { filters: { deviceId: { eq: deviceId } } });
183
189
  }
184
- generateJwt(payload) {
185
- if (payload._orgId !== undefined) {
186
- payload._orgId = String(payload._orgId);
187
- }
190
+ generateJwt(userContext) {
188
191
  const jwtExpiryConfig = config.auth.jwtExpirationInSeconds;
189
192
  const jwtExpirationInSeconds = (typeof jwtExpiryConfig === 'string') ? parseInt(jwtExpiryConfig) : jwtExpiryConfig;
190
- const accessToken = JwtService.sign(payload, config.clientSecret, {
193
+ const accessToken = JwtService.sign(userContext, config.clientSecret, {
191
194
  expiresIn: jwtExpirationInSeconds
192
195
  });
193
196
  return accessToken;
@@ -11,7 +11,7 @@ export class MultiTenantApiService extends GenericApiService {
11
11
  }
12
12
  }
13
13
  prepareQuery(userContext, queryOptions, operations) {
14
- if (!config?.app?.isMultiTenant) {
14
+ if (!config?.app?.isMultiTenant || userContext?.user?._id === 'system') {
15
15
  return super.prepareQuery(userContext, queryOptions, operations);
16
16
  }
17
17
  if (!userContext || !userContext._orgId) {
@@ -28,8 +28,9 @@ export class MultiTenantApiService extends GenericApiService {
28
28
  throw new BadRequestError('A valid userContext was not provided to MultiTenantApiService.prepareEntity');
29
29
  }
30
30
  const preparedEntity = await super.preprocessEntity(userContext, entity, isCreate, allowId);
31
- const orgIdField = this.tenantDecorator.getOrgIdField();
32
- preparedEntity[orgIdField] = userContext._orgId;
31
+ if (isCreate && userContext.user._id !== 'system') {
32
+ preparedEntity._orgId = userContext._orgId;
33
+ }
33
34
  return preparedEntity;
34
35
  }
35
36
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loomcore/api",
3
- "version": "0.1.20",
3
+ "version": "0.1.22",
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": {