@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
|
-
|
|
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 {
|
|
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
|
|
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(
|
|
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(
|
|
27
|
+
createNewTokens(userContext: IUserContext, activeRefreshToken: IRefreshToken): Promise<{
|
|
28
28
|
accessToken: string;
|
|
29
|
-
refreshToken:
|
|
29
|
+
refreshToken: string;
|
|
30
30
|
expiresOn: number;
|
|
31
|
-
}
|
|
32
|
-
getActiveRefreshToken(
|
|
33
|
-
createNewRefreshToken(userId: string, deviceId: string,
|
|
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(
|
|
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
|
|
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,
|
|
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
|
|
65
|
-
|
|
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(
|
|
72
|
-
const refreshToken = req.query.refreshToken;
|
|
73
|
-
const deviceId = this.getDeviceIdFromCookie(req);
|
|
91
|
+
async requestTokenUsingRefreshToken(refreshToken, deviceId) {
|
|
74
92
|
let tokens = null;
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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(
|
|
102
|
-
|
|
103
|
-
const
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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(
|
|
125
|
-
const refreshTokenResult = await this.refreshTokenService.findOne(
|
|
130
|
+
async getActiveRefreshToken(refreshToken, deviceId) {
|
|
131
|
+
const refreshTokenResult = await this.refreshTokenService.findOne(EmptyUserContext, { filters: { token: { eq: refreshToken }, deviceId: { eq: deviceId } } });
|
|
126
132
|
let activeRefreshToken = null;
|
|
127
133
|
if (refreshTokenResult) {
|
|
128
134
|
const now = Date.now();
|
|
@@ -133,8 +139,8 @@ export class AuthService extends GenericApiService {
|
|
|
133
139
|
}
|
|
134
140
|
return activeRefreshToken;
|
|
135
141
|
}
|
|
136
|
-
async createNewRefreshToken(userId, deviceId,
|
|
137
|
-
const expiresOn =
|
|
142
|
+
async createNewRefreshToken(userId, deviceId, orgId) {
|
|
143
|
+
const expiresOn = this.getExpiresOnFromDays(config.auth.refreshTokenExpirationInDays);
|
|
138
144
|
const newRefreshToken = {
|
|
139
145
|
_orgId: orgId,
|
|
140
146
|
token: this.generateRefreshToken(),
|
|
@@ -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(
|
|
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(
|
|
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
|
-
|
|
32
|
-
|
|
31
|
+
if (isCreate && userContext.user._id !== 'system') {
|
|
32
|
+
preparedEntity._orgId = userContext._orgId;
|
|
33
|
+
}
|
|
33
34
|
return preparedEntity;
|
|
34
35
|
}
|
|
35
36
|
}
|