@diagramers/cli 1.0.24 → 1.0.25

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 (46) hide show
  1. package/dist/services/template-updater.d.ts +0 -1
  2. package/dist/services/template-updater.d.ts.map +1 -1
  3. package/dist/services/template-updater.js +2 -41
  4. package/dist/services/template-updater.js.map +1 -1
  5. package/package.json +1 -1
  6. package/templates/api/certs/auth-app-cert.json +0 -13
  7. package/templates/api/main.ts +0 -10
  8. package/templates/api/package.json +0 -70
  9. package/templates/api/src/assets/css/email-template.css +0 -8
  10. package/templates/api/src/assets/images/logo_large.png +0 -0
  11. package/templates/api/src/assets/keys/certificate.pem +0 -22
  12. package/templates/api/src/assets/keys/private-key.pem +0 -28
  13. package/templates/api/src/config/config-interface.ts +0 -191
  14. package/templates/api/src/config/development.ts +0 -145
  15. package/templates/api/src/config/index.ts +0 -59
  16. package/templates/api/src/config/production.ts +0 -145
  17. package/templates/api/src/config/staging.ts +0 -144
  18. package/templates/api/src/config/uat.ts +0 -144
  19. package/templates/api/src/controllers/account-controller.ts +0 -162
  20. package/templates/api/src/entities/audit.ts +0 -12
  21. package/templates/api/src/entities/base-entity.ts +0 -10
  22. package/templates/api/src/entities/user.ts +0 -71
  23. package/templates/api/src/helpers/FrameworkHelper.ts +0 -157
  24. package/templates/api/src/helpers/auth.ts +0 -971
  25. package/templates/api/src/helpers/cronHelper.ts +0 -170
  26. package/templates/api/src/helpers/dbcontext.ts +0 -83
  27. package/templates/api/src/helpers/encryptionHelper.ts +0 -76
  28. package/templates/api/src/helpers/enums.ts +0 -258
  29. package/templates/api/src/helpers/handle-response.ts +0 -49
  30. package/templates/api/src/helpers/httpHelper.ts +0 -75
  31. package/templates/api/src/helpers/mailer.ts +0 -152
  32. package/templates/api/src/helpers/result.ts +0 -47
  33. package/templates/api/src/helpers/string-helper.ts +0 -27
  34. package/templates/api/src/routes/account-routes.ts +0 -37
  35. package/templates/api/src/routes/auth-routes.ts +0 -286
  36. package/templates/api/src/routes/index.ts +0 -92
  37. package/templates/api/src/schemas/audit.ts +0 -36
  38. package/templates/api/src/schemas/otp.ts +0 -52
  39. package/templates/api/src/schemas/session.ts +0 -57
  40. package/templates/api/src/schemas/user.ts +0 -125
  41. package/templates/api/src/server/index.ts +0 -86
  42. package/templates/api/src/server/socket-server-provider.ts +0 -209
  43. package/templates/api/src/services/account-service.ts +0 -243
  44. package/templates/api/src/services/audit-service.ts +0 -56
  45. package/templates/api/tsconfig.json +0 -16
  46. package/templates/api/webpack.config.js +0 -66
@@ -1,971 +0,0 @@
1
- import firebase from 'firebase/compat/app';
2
- import * as firebaseAdmin from 'firebase-admin'
3
- import 'firebase/compat/auth';
4
- import { initializeApp, App } from 'firebase-admin/app';
5
- import { Auth as IAuth, getAuth } from 'firebase-admin/auth';
6
- import { getStorage, getDownloadURL, Storage } from 'firebase-admin/storage';
7
- import { Config } from '../config';
8
- import { AuditMessageType, ResponseCode, Status, AuthProvider, AuthMethod } from './enums';
9
- import { Result } from './result';
10
- const path = require('path');
11
- import { Http2ServerRequest } from 'http2';
12
- import { IUser, AuthProviderInfo, UserSession, OTPCode } from '../entities/user';
13
- import mime = require('mime');
14
- import { UUID, ObjectId } from 'bson';
15
- import * as jwt from 'jsonwebtoken';
16
- import * as crypto from 'crypto';
17
- import { v4 as uuidv4 } from 'uuid';
18
- import dbcontext from './dbcontext';
19
- import { MailHelper } from './mailer';
20
- import { EncryptionHelper } from './encryptionHelper';
21
- import { frameworkHelper } from './FrameworkHelper';
22
-
23
- export class AuthHelper {
24
-
25
- app: App;
26
- auth: IAuth;
27
- authProvider: any
28
- appProvider: any;
29
- result: Result;
30
- className = 'helpers/auth';
31
- storageProvider: Storage;
32
- defaultBucketName = 'gs://sendifier-web-app';
33
- private encryptionHelper: EncryptionHelper;
34
- private frameworkHelper: any;
35
-
36
- constructor(result: Result) {
37
- try {
38
- this.result = result;
39
- this.encryptionHelper = new EncryptionHelper();
40
- this.frameworkHelper = frameworkHelper;
41
-
42
- // Initialize Firebase if enabled
43
- if (Config.props.auth.firebase.enabled) {
44
- var certPath = path.resolve(Config.props.auth.firebase.private_key_path);
45
- result.addMessage(AuditMessageType.trace, this.className, this.className + '- ctor', `certification path ${certPath}`)
46
- var cred = firebaseAdmin.credential.cert(certPath);
47
- if (firebaseAdmin.apps.length > 0) {
48
- this.app = firebaseAdmin.apps[0];
49
- }
50
- else {
51
- firebaseAdmin.initializeApp({
52
- credential: cred
53
- });
54
- console.log('firebase config is', Config.props.auth.firebase)
55
- this.app = firebaseAdmin.app();
56
- }
57
- this.auth = getAuth(this.app);
58
- this.storageProvider = firebaseAdmin.storage(this.app);
59
- }
60
-
61
- }
62
- catch (ex) {
63
- console.log(ex);
64
- // this.result?.addException(this.className, this.className + '- ctor', ex);
65
- // this.result.Data = null;
66
- // this.result.Status = ResponseCode.Unauthorized;
67
- }
68
- }
69
-
70
- async GetUser(uid: string):
71
- Promise<Result> {
72
- try {
73
- var result = await this.auth.getUser(uid);
74
- return new Result(result, ResponseCode.Ok, null, ['firebase user exists']);
75
- }
76
- catch (ex) {
77
- return new Result(null, ResponseCode.Error, [ex], [ex.errorInfo.message]);
78
- }
79
- }
80
-
81
- async CreateUser(user: any):
82
- Promise<Result> {
83
- try {
84
- this.result.addMessage(AuditMessageType.info, this.className, 'CreateUser', 'Started')
85
- var response = await this.auth.createUser(user);
86
- this.result.Data = response;
87
- this.result.Status = ResponseCode.Ok;
88
- this.result.addMessage(AuditMessageType.info, this.className, 'CreateUser', 'Finished');
89
- return this.result;
90
- }
91
- catch (ex) {
92
- this.result.addException(this.className, 'CreateUser', ex);
93
- return this.result;
94
- }
95
- }
96
-
97
- GetUserProfile(req): any {
98
- try {
99
- console.log("authHelper*GetUserProfile** GetUserProfile**** ", req.headers["user-profile"]);
100
- var userProfileHeaderStringified = req.headers["user-profile"].toString();
101
- console.log("*auth**GetUserProfile*** Start checking auth for user**** ", userProfileHeaderStringified)
102
- var userProfileHeader: any = null;
103
- if (userProfileHeaderStringified)
104
- userProfileHeader = JSON.parse(userProfileHeaderStringified);
105
- else
106
- userProfileHeader = null;
107
- return userProfileHeader;
108
- }
109
- catch (ex) {
110
- console.log("authHelper*GetUserProfile** exception**** " + JSON.stringify(ex));
111
- return null;
112
- }
113
- }
114
-
115
- async CheckAuth(req: Http2ServerRequest, res) {
116
- console.log("authHelper*CheckAuth** requestHeaders**** ", req.headers);
117
- var userProfileHeaderStringified = req.headers["user-profile"].toString();
118
- console.log("*auth**CheckAuth*** Start checking auth for user**** ", userProfileHeaderStringified)
119
- var userProfileHeader: any = null;
120
- if (userProfileHeaderStringified)
121
- userProfileHeader = JSON.parse(userProfileHeaderStringified);
122
- else
123
- userProfileHeader = null;
124
- if (userProfileHeader) {
125
- var verifyToken = await this.VerifyToken(userProfileHeader.token);
126
- if (verifyToken.Status == ResponseCode.Ok) {
127
- var result = new Result(verifyToken.Data, ResponseCode.Ok);
128
- return result;
129
- }
130
- else {
131
- var result = new Result(null, ResponseCode.Unauthorized);
132
- res.json(result);
133
- }
134
- }
135
- else {
136
- var result = new Result(null, ResponseCode.Unauthorized);
137
- res.json(result);
138
- }
139
- }
140
-
141
- async VerifyToken(token: string) {
142
- try {
143
- return new Result(true, ResponseCode.Ok);
144
- }
145
- catch (ex) {
146
- console.log("authhelper*verifytoken** exception****", ex);
147
- return new Result(ex.message, ResponseCode.Unauthorized);
148
- }
149
- }
150
-
151
- async RemoveUser(userEmail: string) {
152
- try {
153
- console.log("authhelper*RemoveUser** delete user with email****", userEmail);
154
- var result = await this.auth.getUserByEmail(userEmail);
155
- await this.auth.deleteUser(result.uid);
156
- console.log("authhelper*RemoveUser** delete user done****", result);
157
- return new Result(result, ResponseCode.Ok, null, ['User deleted successfully']);
158
- }
159
- catch (ex) {
160
- console.log("authhelper*RemoveUser** exception****", ex);
161
- return new Result(ex.message, ResponseCode.Error, [ex], [ex.message]);
162
- }
163
- }
164
-
165
- async UploadFileByPath(path: string = null, name: string = null, bucket: string = null) {
166
- try {
167
- if (!bucket)
168
- bucket = this.defaultBucketName;
169
- var storage = getStorage(this.app).bucket(bucket);
170
- return await storage.upload(path);
171
- }
172
- catch (ex) {
173
- this.result.addException(this.className, 'UploadFile', ex);
174
- this.result.Data = null;
175
- this.result.Status = ResponseCode.Error;
176
- return this.result;
177
- }
178
- }
179
-
180
-
181
- async UploadFileByContent(fileBase64: string = null, name: string = null, path: string = null, contentType = null): Promise<Result> {
182
- try {
183
- this.result.addMessage(AuditMessageType.info, this.className, 'UploadFileByContent', 'Started');
184
-
185
- const storageBucket = this.storageProvider.bucket(this.defaultBucketName);
186
- const file = storageBucket.file(`${path}/${new Date().getTime()}_${name}`);
187
- const uuid = new UUID();
188
- const stream = file.createWriteStream({
189
- resumable: true,
190
-
191
- metadata: {
192
- contentType: contentType,
193
- contentDisposition: 'inline', // Allows the file to be viewed in the browser
194
- firebaseStorageDownloadTokens: uuid,
195
- }
196
- });
197
-
198
- await new Promise<any>((resolve, reject) => {
199
- // Handle errors during the upload
200
- stream.on('error', (err) => {
201
- this.result.addException(this.className, 'UploadFileByContent', `Error during upload: ${err}`);
202
- this.result.Data = null;
203
- this.result.Status = ResponseCode.Error;
204
- reject(this.result);
205
- });
206
-
207
- // Handle the upload completion
208
- stream.on('finish', async () => {
209
- try {
210
- await file.generateSignedPostPolicyV2({
211
- expires: '03-09-2025', // Set an expiration date for the URL
212
- })
213
- const downloadUrl = await getDownloadURL(file);
214
- // const [downloadUrl] = await file.getSignedUrl({
215
- // action: 'read',
216
- // expires: '03-09-2025'
217
- // });
218
-
219
- if (downloadUrl) {
220
- this.result.addMessage(AuditMessageType.info, this.className, 'UploadFileByContent', 'Media uploaded');
221
- this.result.Data = downloadUrl;
222
- this.result.Status = ResponseCode.Ok;
223
- resolve(this.result);
224
- } else {
225
- this.result.addMessage(AuditMessageType.error, this.className, 'UploadFileByContent', 'Download URL is null.');
226
- this.result.Data = null;
227
- this.result.Status = ResponseCode.Error;
228
- reject(this.result);
229
- }
230
- } catch (error) {
231
- this.result.addException(this.className, 'UploadFileByContent', `Error getting signed URL: ${error}`);
232
- this.result.Data = null;
233
- this.result.Status = ResponseCode.Error;
234
- reject(this.result);
235
- }
236
- });
237
-
238
- // Write the buffer to the stream
239
- stream.end(fileBase64);
240
- });
241
-
242
- return this.result;
243
- } catch (ex) {
244
- this.result.addException(this.className, 'UploadFileByContent', `Error during file upload: ${ex}`);
245
- this.result.Data = null;
246
- this.result.Status = ResponseCode.Error;
247
- return this.result;
248
- }
249
- }
250
-
251
- async UploadFile(fileBase64: string, name, path, contentType): Promise<Result> {
252
- try {
253
- this.result.addMessage(AuditMessageType.info, this.className, 'UploadFile', `Started`);
254
- const storageBucket = this.storageProvider.bucket(this.defaultBucketName);
255
- const audioBuffer = Buffer.from(fileBase64, 'base64');
256
-
257
- const audioFile = storageBucket.file(`${this.result.user._defaultCompanyId}/${path}/${name}`);
258
- await audioFile.save(audioBuffer, { contentType: contentType, resumable: true });
259
- const url = await getDownloadURL(audioFile);
260
- this.result.addMessage(AuditMessageType.info, this.className, 'UploadFile', `Finished`);
261
- this.result.Data = url;
262
- this.result.Status = ResponseCode.Ok;
263
- return this.result
264
- }
265
- catch (ex) {
266
- this.result.addException(this.className, 'UploadFile', ex);
267
- this.result.Data = null;
268
- this.result.Status = ResponseCode.Error;
269
- return this.result;
270
- }
271
- }
272
-
273
- async UploadFileSigned(fileBase64: string, name: string, path: string, contentType: string): Promise<Result> {
274
- try {
275
- this.result.addMessage(AuditMessageType.info, this.className, 'UploadFile', `Started`);
276
- const storageBucket = this.storageProvider.bucket(this.defaultBucketName);
277
- const audioBuffer = Buffer.from(fileBase64, 'base64');
278
-
279
- const audioFile = storageBucket.file(`${this.result.user._defaultCompanyId}/${path}/${name}`);
280
- await audioFile.save(audioBuffer, { contentType: 'audio/ogg; codecs=opus' });
281
-
282
- type action = "read"
283
- // Correcting the action type to match the GetSignedUrlConfig expected type
284
-
285
- var url = await audioFile.getSignedUrl({ action: 'read', expires: '03-12-2094' })
286
- this.result.addMessage(AuditMessageType.info, this.className, 'UploadFile', `Finished`);
287
- this.result.Data = url[0];
288
- this.result.Status = ResponseCode.Ok;
289
- return this.result;
290
- } catch (ex) {
291
- console.error(`Error in UploadFile: ${ex}`);
292
- this.result.addException(this.className, 'UploadFile', ex);
293
- this.result.Data = null;
294
- this.result.Status = ResponseCode.Error;
295
- return this.result;
296
- }
297
- }
298
-
299
- // ===== MULTI-PROVIDER AUTHENTICATION METHODS =====
300
-
301
- // Internal Authentication
302
- async registerWithPassword(userData: any): Promise<Result> {
303
- try {
304
- this.result.addMessage(AuditMessageType.info, this.className, 'RegisterWithPassword', 'Started');
305
-
306
- // Validate password strength
307
- const passwordValidation = this.validatePassword(userData.password);
308
- if (!passwordValidation.isValid) {
309
- this.result.Status = ResponseCode.Error;
310
- this.result.Errors.push(...passwordValidation.errors);
311
- return this.result;
312
- }
313
-
314
- // Check if user already exists
315
- const existingUser = await dbcontext.UserEntity.findOne({ email: userData.email.toLowerCase() });
316
- if (existingUser) {
317
- this.result.Status = ResponseCode.Duplicate;
318
- this.result.Errors.push('User with this email already exists');
319
- return this.result;
320
- }
321
-
322
- // Create user with internal auth provider
323
- const authProviderInfo: AuthProviderInfo = {
324
- provider: AuthProvider.INTERNAL,
325
- is_verified: false,
326
- last_used: new Date(),
327
- created_at: new Date()
328
- };
329
-
330
- const userEntity = new dbcontext.UserEntity({
331
- _id: new ObjectId(),
332
- name: userData.name,
333
- email: userData.email.toLowerCase(),
334
- username: userData.email.toLowerCase(),
335
- emailVerified: false,
336
- phone: userData.phone || null,
337
- phoneVerified: false,
338
- auth_providers: [authProviderInfo],
339
- primary_auth_provider: AuthProvider.INTERNAL,
340
- hashedPassword: this.encryptionHelper.encryptUserLogin(userData.password),
341
- identity: uuidv4(),
342
- invitationCode: this.frameworkHelper.generateRandomString(6),
343
- status: 2, // PendingConfirmation
344
- userType: "NOR"
345
- });
346
-
347
- const createdUser = await dbcontext.UserEntity.create(userEntity);
348
-
349
- if (createdUser) {
350
- // Send email verification if required
351
- if (Config.props.auth.internal.require_email_verification) {
352
- await this.sendEmailVerification(createdUser);
353
- }
354
-
355
- this.result.Data = { user_id: createdUser._id, message: 'User registered successfully' };
356
- this.result.Status = ResponseCode.Ok;
357
- } else {
358
- this.result.Status = ResponseCode.Error;
359
- this.result.Errors.push('Failed to create user');
360
- }
361
-
362
- return this.result;
363
- } catch (ex) {
364
- this.result.addException(this.className, 'RegisterWithPassword', ex);
365
- return this.result;
366
- }
367
- }
368
-
369
- async loginWithPassword(email: string, password: string, ipAddress: string, userAgent: string): Promise<Result> {
370
- try {
371
- this.result.addMessage(AuditMessageType.info, this.className, 'LoginWithPassword', 'Started');
372
-
373
- const encryptedPassword = this.encryptionHelper.encryptUserLogin(password);
374
- const user = await dbcontext.UserEntity.findOne({
375
- email: email.toLowerCase(),
376
- hashedPassword: encryptedPassword
377
- });
378
-
379
- if (!user) {
380
- this.result.Status = ResponseCode.Unauthorized;
381
- this.result.Errors.push('Invalid credentials');
382
- return this.result;
383
- }
384
-
385
- // Check if user is active
386
- if (user.status !== 1) {
387
- this.result.Status = ResponseCode.Unauthorized;
388
- this.result.Errors.push('Account is not active');
389
- return this.result;
390
- }
391
-
392
- // Create session and generate tokens
393
- const sessionResult = await this.createSession(user, AuthProvider.INTERNAL, AuthMethod.PASSWORD, ipAddress, userAgent);
394
- if (sessionResult.Status !== ResponseCode.Ok) {
395
- return sessionResult;
396
- }
397
-
398
- this.result.Data = {
399
- user: this.sanitizeUserData(user),
400
- session: sessionResult.Data,
401
- message: 'Login successful'
402
- };
403
- this.result.Status = ResponseCode.Ok;
404
-
405
- return this.result;
406
- } catch (ex) {
407
- this.result.addException(this.className, 'LoginWithPassword', ex);
408
- return this.result;
409
- }
410
- }
411
-
412
- // Firebase Authentication
413
- async authenticateWithFirebase(firebaseToken: string, ipAddress: string, userAgent: string): Promise<Result> {
414
- try {
415
- this.result.addMessage(AuditMessageType.info, this.className, 'AuthenticateWithFirebase', 'Started');
416
-
417
- if (!Config.props.auth.firebase.enabled) {
418
- this.result.Status = ResponseCode.Error;
419
- this.result.Errors.push('Firebase authentication is not enabled');
420
- return this.result;
421
- }
422
-
423
- // Verify Firebase token
424
- const decodedToken = await this.auth.verifyIdToken(firebaseToken);
425
-
426
- // Find or create user
427
- let user = await dbcontext.UserEntity.findOne({
428
- 'auth_providers.provider': AuthProvider.FIREBASE,
429
- 'auth_providers.provider_user_id': decodedToken.uid
430
- });
431
-
432
- if (!user) {
433
- // Create new user with Firebase auth
434
- const authProviderInfo: AuthProviderInfo = {
435
- provider: AuthProvider.FIREBASE,
436
- provider_user_id: decodedToken.uid,
437
- is_verified: decodedToken.email_verified || false,
438
- last_used: new Date(),
439
- created_at: new Date(),
440
- provider_metadata: {
441
- email: decodedToken.email,
442
- name: decodedToken.name,
443
- picture: decodedToken.picture
444
- }
445
- };
446
-
447
- user = new dbcontext.UserEntity({
448
- _id: new ObjectId(),
449
- name: decodedToken.name || decodedToken.email,
450
- email: decodedToken.email,
451
- username: decodedToken.email,
452
- emailVerified: decodedToken.email_verified || false,
453
- auth_providers: [authProviderInfo],
454
- primary_auth_provider: AuthProvider.FIREBASE,
455
- firebaseUID: decodedToken.uid,
456
- identity: uuidv4(),
457
- status: 1, // Active
458
- userType: "NOR"
459
- });
460
-
461
- await dbcontext.UserEntity.create(user);
462
- } else {
463
- // Update last used
464
- await dbcontext.UserEntity.updateOne(
465
- { _id: user._id, 'auth_providers.provider': AuthProvider.FIREBASE },
466
- { $set: { 'auth_providers.$.last_used': new Date() } }
467
- );
468
- }
469
-
470
- // Create session
471
- const sessionResult = await this.createSession(user, AuthProvider.FIREBASE, AuthMethod.OAUTH, ipAddress, userAgent);
472
- if (sessionResult.Status !== ResponseCode.Ok) {
473
- return sessionResult;
474
- }
475
-
476
- this.result.Data = {
477
- user: this.sanitizeUserData(user),
478
- session: sessionResult.Data,
479
- message: 'Firebase authentication successful'
480
- };
481
- this.result.Status = ResponseCode.Ok;
482
-
483
- return this.result;
484
- } catch (ex) {
485
- this.result.addException(this.className, 'AuthenticateWithFirebase', ex);
486
- return this.result;
487
- }
488
- }
489
-
490
- // OAuth Authentication
491
- async authenticateWithOAuth(provider: string, accessToken: string, ipAddress: string, userAgent: string): Promise<Result> {
492
- try {
493
- this.result.addMessage(AuditMessageType.info, this.className, 'AuthenticateWithOAuth', `Started with ${provider}`);
494
-
495
- if (!Config.props.auth.oauth.enabled_providers.includes(provider as any)) {
496
- this.result.Status = ResponseCode.Error;
497
- this.result.Errors.push(`${provider} OAuth is not enabled`);
498
- return this.result;
499
- }
500
-
501
- // Get user info from OAuth provider
502
- const userInfo = await this.getOAuthUserInfo(provider, accessToken);
503
- if (!userInfo) {
504
- this.result.Status = ResponseCode.Unauthorized;
505
- this.result.Errors.push('Failed to get user info from OAuth provider');
506
- return this.result;
507
- }
508
-
509
- // Find or create user
510
- let user = await dbcontext.UserEntity.findOne({
511
- 'auth_providers.provider': provider,
512
- 'auth_providers.provider_user_id': userInfo.id
513
- });
514
-
515
- if (!user) {
516
- // Create new user with OAuth auth
517
- const authProviderInfo: AuthProviderInfo = {
518
- provider: provider as AuthProvider,
519
- provider_user_id: userInfo.id,
520
- provider_access_token: accessToken,
521
- is_verified: true,
522
- last_used: new Date(),
523
- created_at: new Date(),
524
- provider_metadata: userInfo
525
- };
526
-
527
- user = new dbcontext.UserEntity({
528
- _id: new ObjectId(),
529
- name: userInfo.name,
530
- email: userInfo.email,
531
- username: userInfo.email,
532
- emailVerified: userInfo.email_verified || true,
533
- auth_providers: [authProviderInfo],
534
- primary_auth_provider: provider as AuthProvider,
535
- identity: uuidv4(),
536
- status: 1, // Active
537
- userType: "NOR"
538
- });
539
-
540
- await dbcontext.UserEntity.create(user);
541
- } else {
542
- // Update access token and last used
543
- await dbcontext.UserEntity.updateOne(
544
- { _id: user._id, 'auth_providers.provider': provider },
545
- {
546
- $set: {
547
- 'auth_providers.$.provider_access_token': accessToken,
548
- 'auth_providers.$.last_used': new Date()
549
- }
550
- }
551
- );
552
- }
553
-
554
- // Create session
555
- const sessionResult = await this.createSession(user, provider as AuthProvider, AuthMethod.OAUTH, ipAddress, userAgent);
556
- if (sessionResult.Status !== ResponseCode.Ok) {
557
- return sessionResult;
558
- }
559
-
560
- this.result.Data = {
561
- user: this.sanitizeUserData(user),
562
- session: sessionResult.Data,
563
- message: `${provider} authentication successful`
564
- };
565
- this.result.Status = ResponseCode.Ok;
566
-
567
- return this.result;
568
- } catch (ex) {
569
- this.result.addException(this.className, 'AuthenticateWithOAuth', ex);
570
- return this.result;
571
- }
572
- }
573
-
574
- // SMS OTP Authentication
575
- async sendSMSOTP(phone: string, purpose: string): Promise<Result> {
576
- try {
577
- this.result.addMessage(AuditMessageType.info, this.className, 'SendSMSOTP', 'Started');
578
-
579
- if (!Config.props.auth.sms_otp.enabled) {
580
- this.result.Status = ResponseCode.Error;
581
- this.result.Errors.push('SMS OTP is not enabled');
582
- return this.result;
583
- }
584
-
585
- // Generate OTP
586
- const otp = this.generateOTP(Config.props.auth.sms_otp.otp_length);
587
- const expiresAt = new Date(Date.now() + Config.props.auth.sms_otp.otp_expiry_minutes * 60 * 1000);
588
-
589
- // Find user by phone
590
- const user = await dbcontext.UserEntity.findOne({ phone });
591
- if (!user) {
592
- this.result.Status = ResponseCode.NotExist;
593
- this.result.Errors.push('User not found with this phone number');
594
- return this.result;
595
- }
596
-
597
- // Create OTP record
598
- const otpEntity = new dbcontext.OTPEntity({
599
- code: otp,
600
- user_id: user._id,
601
- auth_provider: AuthProvider.SMS_OTP,
602
- purpose: purpose,
603
- expires_at: expiresAt,
604
- max_attempts: Config.props.auth.sms_otp.max_attempts
605
- });
606
-
607
- await dbcontext.OTPEntity.create(otpEntity);
608
-
609
- // Send SMS
610
- await this.sendSMS(phone, `Your OTP is: ${otp}. Valid for ${Config.props.auth.sms_otp.otp_expiry_minutes} minutes.`);
611
-
612
- this.result.Data = { message: 'OTP sent successfully' };
613
- this.result.Status = ResponseCode.Ok;
614
-
615
- return this.result;
616
- } catch (ex) {
617
- this.result.addException(this.className, 'SendSMSOTP', ex);
618
- return this.result;
619
- }
620
- }
621
-
622
- async verifySMSOTP(phone: string, otp: string, ipAddress: string, userAgent: string): Promise<Result> {
623
- try {
624
- this.result.addMessage(AuditMessageType.info, this.className, 'VerifySMSOTP', 'Started');
625
-
626
- // Find user by phone
627
- const user = await dbcontext.UserEntity.findOne({ phone });
628
- if (!user) {
629
- this.result.Status = ResponseCode.NotExist;
630
- this.result.Errors.push('User not found');
631
- return this.result;
632
- }
633
-
634
- // Find and verify OTP
635
- const otpRecord = await dbcontext.OTPEntity.findOne({
636
- user_id: user._id,
637
- auth_provider: AuthProvider.SMS_OTP,
638
- code: otp,
639
- is_used: false,
640
- expires_at: { $gt: new Date() }
641
- });
642
-
643
- if (!otpRecord) {
644
- this.result.Status = ResponseCode.Unauthorized;
645
- this.result.Errors.push('Invalid or expired OTP');
646
- return this.result;
647
- }
648
-
649
- // Check attempts
650
- if (otpRecord.attempts >= otpRecord.max_attempts) {
651
- this.result.Status = ResponseCode.Unauthorized;
652
- this.result.Errors.push('Maximum OTP attempts exceeded');
653
- return this.result;
654
- }
655
-
656
- // Mark OTP as used
657
- await dbcontext.OTPEntity.updateOne(
658
- { _id: otpRecord._id },
659
- { $set: { is_used: true, attempts: otpRecord.attempts + 1 } }
660
- );
661
-
662
- // Update user phone verification
663
- await dbcontext.UserEntity.updateOne(
664
- { _id: user._id },
665
- { $set: { phoneVerified: true } }
666
- );
667
-
668
- // Create session
669
- const sessionResult = await this.createSession(user, AuthProvider.SMS_OTP, AuthMethod.OTP, ipAddress, userAgent);
670
- if (sessionResult.Status !== ResponseCode.Ok) {
671
- return sessionResult;
672
- }
673
-
674
- this.result.Data = {
675
- user: this.sanitizeUserData(user),
676
- session: sessionResult.Data,
677
- message: 'SMS OTP verification successful'
678
- };
679
- this.result.Status = ResponseCode.Ok;
680
-
681
- return this.result;
682
- } catch (ex) {
683
- this.result.addException(this.className, 'VerifySMSOTP', ex);
684
- return this.result;
685
- }
686
- }
687
-
688
- // Email OTP Authentication
689
- async sendEmailOTP(email: string, purpose: string): Promise<Result> {
690
- try {
691
- this.result.addMessage(AuditMessageType.info, this.className, 'SendEmailOTP', 'Started');
692
-
693
- if (!Config.props.auth.email_otp.enabled) {
694
- this.result.Status = ResponseCode.Error;
695
- this.result.Errors.push('Email OTP is not enabled');
696
- return this.result;
697
- }
698
-
699
- // Generate OTP
700
- const otp = this.generateOTP(Config.props.auth.email_otp.otp_length);
701
- const expiresAt = new Date(Date.now() + Config.props.auth.email_otp.otp_expiry_minutes * 60 * 1000);
702
-
703
- // Find user by email
704
- const user = await dbcontext.UserEntity.findOne({ email: email.toLowerCase() });
705
- if (!user) {
706
- this.result.Status = ResponseCode.NotExist;
707
- this.result.Errors.push('User not found with this email');
708
- return this.result;
709
- }
710
-
711
- // Create OTP record
712
- const otpEntity = new dbcontext.OTPEntity({
713
- code: otp,
714
- user_id: user._id,
715
- auth_provider: AuthProvider.EMAIL_OTP,
716
- purpose: purpose,
717
- expires_at: expiresAt,
718
- max_attempts: Config.props.auth.email_otp.max_attempts
719
- });
720
-
721
- await dbcontext.OTPEntity.create(otpEntity);
722
-
723
- // Send email
724
- const emailContent = MailHelper.GetEmailConfirmationTemplate(user.name, `Your OTP is: ${otp}. Valid for ${Config.props.auth.email_otp.otp_expiry_minutes} minutes.`, 'Verify OTP');
725
- await MailHelper.SendEmail(email, emailContent, 'Email OTP Verification');
726
-
727
- this.result.Data = { message: 'Email OTP sent successfully' };
728
- this.result.Status = ResponseCode.Ok;
729
-
730
- return this.result;
731
- } catch (ex) {
732
- this.result.addException(this.className, 'SendEmailOTP', ex);
733
- return this.result;
734
- }
735
- }
736
-
737
- // Session Management
738
- async createSession(user: IUser, authProvider: AuthProvider, authMethod: AuthMethod, ipAddress: string, userAgent: string): Promise<Result> {
739
- try {
740
- // Check session limits
741
- const activeSessions = await dbcontext.SessionEntity.countDocuments({
742
- user_id: user._id,
743
- is_active: true
744
- });
745
-
746
- if (activeSessions >= Config.props.auth.session.max_sessions_per_user && !Config.props.auth.session.concurrent_login_allowed) {
747
- // Deactivate oldest session
748
- const oldestSession = await dbcontext.SessionEntity.findOne({
749
- user_id: user._id,
750
- is_active: true
751
- }).sort({ created_at: 1 });
752
-
753
- if (oldestSession) {
754
- await dbcontext.SessionEntity.updateOne(
755
- { _id: oldestSession._id },
756
- { $set: { is_active: false } }
757
- );
758
- }
759
- }
760
-
761
- // Create new session
762
- const sessionId = uuidv4();
763
- const expiresAt = new Date(Date.now() + Config.props.auth.session.session_timeout_minutes * 60 * 1000);
764
-
765
- const sessionEntity = new dbcontext.SessionEntity({
766
- session_id: sessionId,
767
- user_id: user._id,
768
- auth_provider: authProvider,
769
- auth_method: authMethod,
770
- ip_address: ipAddress,
771
- user_agent: userAgent,
772
- expires_at: expiresAt
773
- });
774
-
775
- await dbcontext.SessionEntity.create(sessionEntity);
776
-
777
- // Generate JWT token
778
- const token = jwt.sign(
779
- {
780
- user_id: user._id.toString(),
781
- session_id: sessionId,
782
- auth_provider: authProvider,
783
- auth_method: authMethod
784
- },
785
- Config.props.auth.jwt.secret,
786
- {
787
- expiresIn: Config.props.auth.jwt.expires_in,
788
- issuer: Config.props.auth.jwt.issuer,
789
- audience: Config.props.auth.jwt.audience
790
- } as any
791
- );
792
-
793
- const refreshToken = jwt.sign(
794
- {
795
- user_id: user._id.toString(),
796
- session_id: sessionId,
797
- type: 'refresh'
798
- },
799
- Config.props.auth.jwt.secret,
800
- {
801
- expiresIn: Config.props.auth.jwt.refresh_token_expires_in,
802
- issuer: Config.props.auth.jwt.issuer,
803
- audience: Config.props.auth.jwt.audience
804
- } as any
805
- );
806
-
807
- return new Result({
808
- session_id: sessionId,
809
- token: token,
810
- refresh_token: refreshToken,
811
- expires_at: expiresAt
812
- }, ResponseCode.Ok);
813
- } catch (ex) {
814
- return new Result(null, ResponseCode.Error, [ex], [ex.message]);
815
- }
816
- }
817
-
818
- async verifySession(sessionId: string, token: string): Promise<Result> {
819
- try {
820
- // Verify JWT token
821
- const decoded = jwt.verify(token, Config.props.auth.jwt.secret) as any;
822
-
823
- // Check session
824
- const session = await dbcontext.SessionEntity.findOne({
825
- session_id: sessionId,
826
- user_id: decoded.user_id,
827
- is_active: true,
828
- expires_at: { $gt: new Date() }
829
- });
830
-
831
- if (!session) {
832
- return new Result(null, ResponseCode.Unauthorized, [], ['Invalid or expired session']);
833
- }
834
-
835
- // Update last activity
836
- await dbcontext.SessionEntity.updateOne(
837
- { _id: session._id },
838
- { $set: { last_activity: new Date() } }
839
- );
840
-
841
- // Get user
842
- const user = await dbcontext.UserEntity.findById(decoded.user_id);
843
- if (!user) {
844
- return new Result(null, ResponseCode.NotExist, [], ['User not found']);
845
- }
846
-
847
- return new Result({
848
- user: this.sanitizeUserData(user),
849
- session: session
850
- }, ResponseCode.Ok);
851
- } catch (ex) {
852
- return new Result(null, ResponseCode.Unauthorized, [ex], [ex.message]);
853
- }
854
- }
855
-
856
- async logout(sessionId: string): Promise<Result> {
857
- try {
858
- await dbcontext.SessionEntity.updateOne(
859
- { session_id: sessionId },
860
- { $set: { is_active: false } }
861
- );
862
-
863
- return new Result({ message: 'Logged out successfully' }, ResponseCode.Ok);
864
- } catch (ex) {
865
- return new Result(null, ResponseCode.Error, [ex], [ex.message]);
866
- }
867
- }
868
-
869
- // Utility Methods
870
- private validatePassword(password: string): { isValid: boolean; errors: string[] } {
871
- const errors: string[] = [];
872
- const config = Config.props.auth.internal;
873
-
874
- if (password.length < config.password_min_length) {
875
- errors.push(`Password must be at least ${config.password_min_length} characters long`);
876
- }
877
-
878
- if (config.password_require_uppercase && !/[A-Z]/.test(password)) {
879
- errors.push('Password must contain at least one uppercase letter');
880
- }
881
-
882
- if (config.password_require_lowercase && !/[a-z]/.test(password)) {
883
- errors.push('Password must contain at least one lowercase letter');
884
- }
885
-
886
- if (config.password_require_numbers && !/\d/.test(password)) {
887
- errors.push('Password must contain at least one number');
888
- }
889
-
890
- if (config.password_require_special_chars && !/[!@#$%^&*(),.?":{}|<>]/.test(password)) {
891
- errors.push('Password must contain at least one special character');
892
- }
893
-
894
- return { isValid: errors.length === 0, errors };
895
- }
896
-
897
- private generateOTP(length: number): string {
898
- return Math.random().toString().substr(2, length);
899
- }
900
-
901
- private sanitizeUserData(user: IUser): any {
902
- return {
903
- _id: user._id,
904
- name: user.name,
905
- email: user.email,
906
- username: user.username,
907
- emailVerified: user.emailVerified,
908
- phone: user.phone,
909
- phoneVerified: user.phoneVerified,
910
- primary_auth_provider: user.primary_auth_provider,
911
- auth_providers: user.auth_providers.map(provider => ({
912
- provider: provider.provider,
913
- is_verified: provider.is_verified,
914
- last_used: provider.last_used
915
- })),
916
- userType: user.userType,
917
- status: user.status,
918
- createdAt: user.createdAt,
919
- updatedAt: user.updatedAt
920
- };
921
- }
922
-
923
- private async sendEmailVerification(user: IUser): Promise<void> {
924
- const verificationLink = `${Config.props.base_site_url}/verify-email?token=${this.encryptionHelper.encryptUserLogin(user.invitationCode)}&email=${this.encryptionHelper.encryptUserLogin(user.email)}&usrStat=${this.encryptionHelper.encryptUserLogin(user.status)}`;
925
- const emailContent = MailHelper.GetEmailConfirmationTemplate(user.name, verificationLink, 'Verify Email');
926
- await MailHelper.SendEmail(user.email, emailContent, 'Email Verification');
927
- }
928
-
929
- private async getOAuthUserInfo(provider: string, accessToken: string): Promise<any> {
930
- // Implementation would vary by provider
931
- // This is a placeholder - you'd implement specific OAuth provider APIs
932
- switch (provider) {
933
- case 'google':
934
- // Call Google OAuth API
935
- break;
936
- case 'facebook':
937
- // Call Facebook OAuth API
938
- break;
939
- case 'github':
940
- // Call GitHub OAuth API
941
- break;
942
- // ... other providers
943
- }
944
- return null;
945
- }
946
-
947
- private async sendSMS(phone: string, message: string): Promise<void> {
948
- const config = Config.props.auth.sms_otp;
949
-
950
- if (config.provider === 'twilio' && config.twilio) {
951
- const twilio = require('twilio');
952
- const client = twilio(config.twilio.account_sid, config.twilio.auth_token);
953
- await client.messages.create({
954
- body: message,
955
- from: config.twilio.from_number,
956
- to: phone
957
- });
958
- } else if (config.provider === 'aws_sns' && config.aws_sns) {
959
- const AWS = require('aws-sdk');
960
- const sns = new AWS.SNS({
961
- accessKeyId: config.aws_sns.access_key_id,
962
- secretAccessKey: config.aws_sns.secret_access_key,
963
- region: config.aws_sns.region
964
- });
965
- await sns.publish({
966
- Message: message,
967
- PhoneNumber: phone
968
- }).promise();
969
- }
970
- }
971
- }