1 | "use strict";
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | const tslib_1 = require("tslib");
|
4 | const common_1 = require("@nestjs/common");
|
5 | const bcrypt = require("bcryptjs");
|
6 | const emails = require("email-addresses");
|
7 | const t = require("io-ts");
|
8 | const io_ts_reporters_1 = require("io-ts-reporters");
|
9 | const lodash_1 = require("lodash");
|
10 | const __1 = require("..");
|
11 | const configuration_1 = require("../configuration");
|
12 | const auth_repository_1 = require("./auth.repository");
|
13 | const user_service_1 = require("./user.service");
|
14 | const auth_callbacks_1 = require("./auth.callbacks");
|
15 | const userProfile = t.interface({
|
16 | id: t.string,
|
17 | emails: t.array(t.interface({
|
18 | value: t.string,
|
19 | verified: t.boolean,
|
20 | })),
|
21 | displayName: t.string,
|
22 | });
|
23 | class AuthenticationFailedException extends common_1.HttpException {
|
24 | constructor(message) {
|
25 | super(message, common_1.HttpStatus.UNAUTHORIZED);
|
26 | }
|
27 | }
|
28 | exports.AuthenticationFailedException = AuthenticationFailedException;
|
29 | const PASSWORD_ROUNDS = 10;
|
30 | async function hashPassword(password) {
|
31 | return await bcrypt.hash(password, PASSWORD_ROUNDS);
|
32 | }
|
33 | exports.hashPassword = hashPassword;
|
34 | let AuthService = class AuthService {
|
35 | constructor(authRepository, userService, configurationProvider, authCallbacks) {
|
36 | this.authRepository = authRepository;
|
37 | this.userService = userService;
|
38 | this.configurationProvider = configurationProvider;
|
39 | this.authCallbacks = authCallbacks;
|
40 | this.logger = __1.createLogger('account-service');
|
41 | }
|
42 | async validateUser(context, username, password) {
|
43 | const account = await this.getAccountByEmail(context, username);
|
44 | if (!account) {
|
45 | throw new AuthenticationFailedException('No credentials found for user');
|
46 | }
|
47 | if (account.type !== 'password') {
|
48 | throw new AuthenticationFailedException('No credentials found for user');
|
49 | }
|
50 | const result = await bcrypt.compare(password, account.password);
|
51 | if (!result) {
|
52 | throw new AuthenticationFailedException(`Invalid password for user`);
|
53 | }
|
54 | return await this.loadUserAndCheckEnabled(context, account.userId);
|
55 | }
|
56 | async validateFakeLogin(context, secret, email, name, roles, orgId, props) {
|
57 | this.logger.info(`Validating fake login for ${email}`);
|
58 | const configSecret = this.configurationProvider.auth.fake.secret;
|
59 | if (configSecret && configSecret !== secret) {
|
60 | throw new AuthenticationFailedException('Fake login secret invalid');
|
61 | }
|
62 | const user = await this.userService.getByEmail(context, email);
|
63 | if (user) {
|
64 | if (!user.enabled) {
|
65 | throw new AuthenticationFailedException('User account is disabled');
|
66 | }
|
67 | return await this.userService.update(context, user.id, Object.assign({}, user, { name,
|
68 | roles,
|
69 | orgId,
|
70 | props }));
|
71 | }
|
72 | else {
|
73 | return await this.userService.create(context, {
|
74 | email,
|
75 | name,
|
76 | roles,
|
77 | orgId,
|
78 | props,
|
79 | enabled: true,
|
80 | });
|
81 | }
|
82 | }
|
83 | async validateUserGoogle(context, inputProfile) {
|
84 | const validationResult = userProfile.decode(inputProfile);
|
85 | if (validationResult.isLeft()) {
|
86 | throw new Error(io_ts_reporters_1.reporter(validationResult).join(', '));
|
87 | }
|
88 | const profile = validationResult.value;
|
89 | const accountEmails = profile.emails.find(accountEmail => accountEmail.verified);
|
90 | if (!accountEmails) {
|
91 | throw new AuthenticationFailedException('No credentials found for user');
|
92 | }
|
93 | const email = accountEmails.value;
|
94 | const account = await this.getAccountByEmail(context, email);
|
95 | if (!account) {
|
96 | if (!this.configurationProvider.auth.google || !this.configurationProvider.auth.google.signUpEnabled) {
|
97 | throw new AuthenticationFailedException('No credentials found for user');
|
98 | }
|
99 | const { domain } = emails.parseOneAddress(email);
|
100 | const signUpDomains = this.configurationProvider.auth.google.signUpDomains || [];
|
101 | if (!signUpDomains.includes(domain)) {
|
102 | throw new AuthenticationFailedException('No credentials found for user');
|
103 | }
|
104 | const createdUser = await this.userService.create(context, {
|
105 | roles: this.configurationProvider.auth.google.signUpRoles,
|
106 | email,
|
107 | name: profile.displayName,
|
108 | enabled: true,
|
109 | });
|
110 | await this.authRepository.save(context, {
|
111 | id: email,
|
112 | type: 'google',
|
113 | userId: createdUser.id,
|
114 | });
|
115 | return createdUser;
|
116 | }
|
117 | if (account.type !== 'google' && account.type !== 'password') {
|
118 | throw new AuthenticationFailedException('No credentials found for user');
|
119 | }
|
120 | return await this.loadUserAndCheckEnabled(context, account.userId);
|
121 | }
|
122 | async validateUserSaml(context, profile) {
|
123 | return this.validateOrCreateExternalAuthAccount(context, profile.email, {
|
124 | type: 'saml',
|
125 | newUserRequest: () => ({
|
126 | roles: [],
|
127 | email: profile.email,
|
128 | name: this.toName(profile),
|
129 | enabled: true,
|
130 | }),
|
131 | });
|
132 | }
|
133 | async validateUserOidc(context, profile, overwriteCredentials) {
|
134 | const profileJson = profile['_json'];
|
135 | const email = profile.email || (profileJson && profileJson.email);
|
136 | const roles = this.buildUserRoles(profile, []);
|
137 | const props = this.buildUserProperties(profile, {});
|
138 | return this.validateOrCreateExternalAuthAccount(context, email, {
|
139 | type: 'oidc',
|
140 | overwriteCredentials,
|
141 | newUserRequest: () => ({
|
142 | email,
|
143 | name: profile.displayName,
|
144 | roles,
|
145 | props,
|
146 | enabled: true,
|
147 | }),
|
148 | updateUser: user => {
|
149 | return this.userService.update(context, user.id, Object.assign({}, user, { roles,
|
150 | props, name: profile.displayName }));
|
151 | },
|
152 | });
|
153 | }
|
154 | buildUserRoles(ipdProfileData, defaultRoles = []) {
|
155 | if (this.authCallbacks && this.authCallbacks.buildUserRolesList) {
|
156 | return this.authCallbacks.buildUserRolesList(ipdProfileData);
|
157 | }
|
158 | return defaultRoles;
|
159 | }
|
160 | buildUserProperties(ipdProfileData, defaultProperties = {}) {
|
161 | if (this.authCallbacks && this.authCallbacks.buildUserPropertiesObject) {
|
162 | return this.authCallbacks.buildUserPropertiesObject(ipdProfileData);
|
163 | }
|
164 | return defaultProperties;
|
165 | }
|
166 | async validateUserAuth0(context, email, name, orgId, roles, props) {
|
167 | return this.validateOrCreateExternalAuthAccount(context, email, {
|
168 | type: 'auth0',
|
169 | newUserRequest: () => ({
|
170 | roles,
|
171 | orgId,
|
172 | email,
|
173 | name,
|
174 | props,
|
175 | enabled: true,
|
176 | }),
|
177 | updateUser: user => {
|
178 | user.name = name;
|
179 | user.roles = roles;
|
180 | user.orgId = orgId;
|
181 | user.props = props;
|
182 | return this.userService.update(context, user.id, user);
|
183 | },
|
184 | });
|
185 | }
|
186 | async createAccount(context, email, password, account) {
|
187 | const existingCredentials = await this.getAccountByEmail(context, email);
|
188 | if (!existingCredentials) {
|
189 | return await this.authRepository.save(context, {
|
190 | id: email,
|
191 | password: await hashPassword(password),
|
192 | userId: account,
|
193 | type: 'password',
|
194 | });
|
195 | }
|
196 | return existingCredentials;
|
197 | }
|
198 | async validateOrCreateExternalAuthAccount(context, email, options) {
|
199 | const { newUserRequest, updateUser, type } = options;
|
200 | this.logger.info(`Validating ${type} user profile`);
|
201 | const account = await this.getAccountByEmail(context, email);
|
202 | if (!account) {
|
203 | this.logger.info(`No login credentials found for ${email}, creating credentials and creating or updating user.`);
|
204 | const updatedUser = await this.userService.createOrUpdate(context, newUserRequest(), this.validateUserEnabled);
|
205 | await this.authRepository.save(context, {
|
206 | id: email,
|
207 | type,
|
208 | userId: updatedUser.id,
|
209 | });
|
210 | return updatedUser;
|
211 | }
|
212 | if (!options.overwriteCredentials && account.type !== type) {
|
213 | throw new AuthenticationFailedException('No credentials found for user');
|
214 | }
|
215 | const user = await this.loadUserAndCheckEnabled(context, account.userId);
|
216 | if (account.type !== type) {
|
217 | this.logger.info(`Updating auth type to [${type}] for [${email}]`);
|
218 | await this.authRepository.save(context, {
|
219 | id: account.id,
|
220 | type,
|
221 | userId: account.userId,
|
222 | });
|
223 | }
|
224 | this.logger.info(`User ${email} validated`);
|
225 | return updateUser ? await updateUser(user) : user;
|
226 | }
|
227 | async loadUserAndCheckEnabled(context, userId) {
|
228 | const user = await this.userService.get(context, userId);
|
229 | if (!user) {
|
230 | throw new AuthenticationFailedException('User not found');
|
231 | }
|
232 | this.validateUserEnabled(user);
|
233 | return user;
|
234 | }
|
235 | validateUserEnabled(user) {
|
236 | if (!user.enabled) {
|
237 | throw new AuthenticationFailedException('User account is disabled');
|
238 | }
|
239 | }
|
240 | getAccountByEmail(context, email) {
|
241 | const normalisedEmail = __1.normaliseEmail(email);
|
242 | this.logger.info(`Looking up user by email ${normalisedEmail}`);
|
243 | return this.authRepository.get(context, normalisedEmail);
|
244 | }
|
245 | toName(profile) {
|
246 | return [profile.firstName, profile.lastName].filter(part => !lodash_1.isNil(part)).join(' ');
|
247 | }
|
248 | };
|
249 | tslib_1.__decorate([
|
250 | __1.Transactional(),
|
251 | tslib_1.__metadata("design:type", Function),
|
252 | tslib_1.__metadata("design:paramtypes", [Object, Object, String, String, Array, String, Object]),
|
253 | tslib_1.__metadata("design:returntype", Promise)
|
254 | ], AuthService.prototype, "validateFakeLogin", null);
|
255 | tslib_1.__decorate([
|
256 | __1.Transactional(),
|
257 | tslib_1.__metadata("design:type", Function),
|
258 | tslib_1.__metadata("design:paramtypes", [Object, Object]),
|
259 | tslib_1.__metadata("design:returntype", Promise)
|
260 | ], AuthService.prototype, "validateUserGoogle", null);
|
261 | tslib_1.__decorate([
|
262 | __1.Transactional(),
|
263 | tslib_1.__metadata("design:type", Function),
|
264 | tslib_1.__metadata("design:paramtypes", [Object, Object]),
|
265 | tslib_1.__metadata("design:returntype", Promise)
|
266 | ], AuthService.prototype, "validateUserSaml", null);
|
267 | tslib_1.__decorate([
|
268 | __1.Transactional(),
|
269 | tslib_1.__metadata("design:type", Function),
|
270 | tslib_1.__metadata("design:paramtypes", [Object, Object, Boolean]),
|
271 | tslib_1.__metadata("design:returntype", Promise)
|
272 | ], AuthService.prototype, "validateUserOidc", null);
|
273 | tslib_1.__decorate([
|
274 | __1.Transactional(),
|
275 | tslib_1.__metadata("design:type", Function),
|
276 | tslib_1.__metadata("design:paramtypes", [Object, String, String, String, Array, Object]),
|
277 | tslib_1.__metadata("design:returntype", Promise)
|
278 | ], AuthService.prototype, "validateUserAuth0", null);
|
279 | tslib_1.__decorate([
|
280 | __1.Transactional(),
|
281 | tslib_1.__metadata("design:type", Function),
|
282 | tslib_1.__metadata("design:paramtypes", [Object, String, String, String]),
|
283 | tslib_1.__metadata("design:returntype", Promise)
|
284 | ], AuthService.prototype, "createAccount", null);
|
285 | AuthService = tslib_1.__decorate([
|
286 | common_1.Injectable(),
|
287 | tslib_1.__param(1, common_1.Inject(user_service_1.USER_SERVICE)),
|
288 | tslib_1.__param(2, common_1.Inject(configuration_1.CONFIGURATION)),
|
289 | tslib_1.__param(3, common_1.Inject(auth_callbacks_1.AUTH_CALLBACKS)),
|
290 | tslib_1.__metadata("design:paramtypes", [auth_repository_1.CredentialRepository, Object, Object, Object])
|
291 | ], AuthService);
|
292 | exports.AuthService = AuthService;
|
293 |
|
\ | No newline at end of file |