@loomcore/api 0.1.20 → 0.1.21
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,20 @@ 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 userContext = req.userContext;
|
|
42
|
+
const refreshToken = req.query.refreshToken;
|
|
43
|
+
if (!userContext || !refreshToken || typeof refreshToken !== 'string') {
|
|
44
|
+
throw new BadRequestError('Missing required fields: userContext and refreshToken are required.');
|
|
45
|
+
}
|
|
46
|
+
const deviceId = this.authService.getDeviceIdFromCookie(req);
|
|
47
|
+
const tokens = await this.authService.requestTokenUsingRefreshToken(userContext, refreshToken, deviceId);
|
|
39
48
|
if (tokens) {
|
|
40
49
|
apiUtils.apiResponse(res, 200, { data: tokens }, TokenResponseSpec);
|
|
41
50
|
}
|
|
@@ -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(userContext: IUserContext, 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
|
-
}
|
|
31
|
+
}>;
|
|
32
32
|
getActiveRefreshToken(userContext: IUserContext, refreshToken: string, deviceId: string): Promise<IRefreshToken | null>;
|
|
33
33
|
createNewRefreshToken(userId: string, deviceId: string, existingExpiresOn?: number | null, 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();
|
|
@@ -56,31 +60,39 @@ 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(userContext, refreshToken, deviceId) {
|
|
74
92
|
let tokens = null;
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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(userContext, refreshToken, deviceId);
|
|
94
|
+
if (activeRefreshToken) {
|
|
95
|
+
tokens = await this.createNewTokens(userContext, activeRefreshToken);
|
|
84
96
|
}
|
|
85
97
|
return tokens;
|
|
86
98
|
}
|
|
@@ -98,27 +110,15 @@ export class AuthService extends GenericApiService {
|
|
|
98
110
|
};
|
|
99
111
|
return result;
|
|
100
112
|
}
|
|
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
|
-
}
|
|
113
|
+
async createNewTokens(userContext, activeRefreshToken) {
|
|
114
|
+
const payload = userContext;
|
|
115
|
+
const accessToken = this.generateJwt(payload);
|
|
116
|
+
const accessTokenExpiresOn = this.getExpiresOnFromSeconds(config.auth.jwtExpirationInSeconds);
|
|
117
|
+
const tokenResponse = {
|
|
118
|
+
accessToken,
|
|
119
|
+
refreshToken: activeRefreshToken.token,
|
|
120
|
+
expiresOn: accessTokenExpiresOn
|
|
121
|
+
};
|
|
122
122
|
return tokenResponse;
|
|
123
123
|
}
|
|
124
124
|
async getActiveRefreshToken(userContext, refreshToken, deviceId) {
|
|
@@ -181,13 +181,10 @@ export class AuthService extends GenericApiService {
|
|
|
181
181
|
deleteRefreshTokensForDevice(deviceId) {
|
|
182
182
|
return this.refreshTokenService.deleteMany(EmptyUserContext, { filters: { deviceId: { eq: deviceId } } });
|
|
183
183
|
}
|
|
184
|
-
generateJwt(
|
|
185
|
-
if (payload._orgId !== undefined) {
|
|
186
|
-
payload._orgId = String(payload._orgId);
|
|
187
|
-
}
|
|
184
|
+
generateJwt(userContext) {
|
|
188
185
|
const jwtExpiryConfig = config.auth.jwtExpirationInSeconds;
|
|
189
186
|
const jwtExpirationInSeconds = (typeof jwtExpiryConfig === 'string') ? parseInt(jwtExpiryConfig) : jwtExpiryConfig;
|
|
190
|
-
const accessToken = JwtService.sign(
|
|
187
|
+
const accessToken = JwtService.sign(userContext, config.clientSecret, {
|
|
191
188
|
expiresIn: jwtExpirationInSeconds
|
|
192
189
|
});
|
|
193
190
|
return accessToken;
|
|
@@ -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
|
}
|