@flusys/nestjs-auth 7.0.1 → 7.1.0

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 (108) hide show
  1. package/README.md +67 -0
  2. package/cjs/config/auth.constants.js +8 -0
  3. package/cjs/config/event-actions.js +4 -2
  4. package/cjs/config/message-keys.js +27 -0
  5. package/cjs/controllers/index.js +2 -0
  6. package/cjs/controllers/social-auth-config.controller.js +107 -0
  7. package/cjs/controllers/social-auth.controller.js +175 -0
  8. package/cjs/controllers/social-auth.controller.spec.js +79 -0
  9. package/cjs/docs/auth-swagger.config.js +9 -1
  10. package/cjs/dtos/index.js +1 -0
  11. package/cjs/dtos/social-auth.dto.js +318 -0
  12. package/cjs/entities/index.js +3 -1
  13. package/cjs/entities/index.spec.js +12 -1
  14. package/cjs/entities/social-auth-config.entity.js +102 -0
  15. package/cjs/enums/index.js +1 -0
  16. package/cjs/enums/social-provider-type.enum.js +17 -0
  17. package/cjs/index.js +1 -0
  18. package/cjs/interfaces/index.js +1 -0
  19. package/cjs/interfaces/social-auth.interface.js +4 -0
  20. package/cjs/modules/auth.module.js +6 -0
  21. package/cjs/modules/auth.module.spec.js +15 -0
  22. package/cjs/providers/facebook.social-provider.js +73 -0
  23. package/cjs/providers/google.social-provider.js +68 -0
  24. package/cjs/providers/index.js +18 -0
  25. package/cjs/providers/linkedin.social-provider.js +66 -0
  26. package/cjs/providers/microsoft.social-provider.js +78 -0
  27. package/cjs/providers/oauth-http.util.js +107 -0
  28. package/cjs/providers/social-provider.registry.js +24 -0
  29. package/cjs/providers/social-provider.registry.spec.js +135 -0
  30. package/cjs/services/auth-config.service.js +3 -0
  31. package/cjs/services/auth-config.service.spec.js +3 -0
  32. package/cjs/services/auth-datasource.provider.js +4 -0
  33. package/cjs/services/auth-datasource.provider.spec.js +30 -0
  34. package/cjs/services/authentication.service.js +38 -8
  35. package/cjs/services/authentication.service.spec.js +58 -0
  36. package/cjs/services/index.js +2 -0
  37. package/cjs/services/social-auth-config.service.js +103 -0
  38. package/cjs/services/social-auth-config.service.spec.js +71 -0
  39. package/cjs/services/social-auth.service.js +310 -0
  40. package/cjs/services/social-auth.service.spec.js +310 -0
  41. package/config/auth.constants.d.ts +2 -0
  42. package/config/event-actions.d.ts +2 -0
  43. package/config/message-keys.d.ts +21 -0
  44. package/controllers/index.d.ts +2 -0
  45. package/controllers/social-auth-config.controller.d.ts +22 -0
  46. package/controllers/social-auth.controller.d.ts +11 -0
  47. package/dtos/index.d.ts +1 -0
  48. package/dtos/social-auth.dto.d.ts +42 -0
  49. package/entities/index.d.ts +3 -1
  50. package/entities/social-auth-config.entity.d.ts +9 -0
  51. package/enums/index.d.ts +1 -0
  52. package/enums/social-provider-type.enum.d.ts +6 -0
  53. package/fesm/config/auth.constants.js +3 -0
  54. package/fesm/config/event-actions.js +4 -2
  55. package/fesm/config/message-keys.js +21 -0
  56. package/fesm/controllers/index.js +2 -0
  57. package/fesm/controllers/social-auth-config.controller.js +97 -0
  58. package/fesm/controllers/social-auth.controller.js +165 -0
  59. package/fesm/controllers/social-auth.controller.spec.js +75 -0
  60. package/fesm/docs/auth-swagger.config.js +9 -1
  61. package/fesm/dtos/index.js +1 -0
  62. package/fesm/dtos/social-auth.dto.js +282 -0
  63. package/fesm/entities/index.js +4 -1
  64. package/fesm/entities/index.spec.js +12 -1
  65. package/fesm/entities/social-auth-config.entity.js +92 -0
  66. package/fesm/enums/index.js +1 -0
  67. package/fesm/enums/social-provider-type.enum.js +7 -0
  68. package/fesm/index.js +1 -0
  69. package/fesm/interfaces/index.js +1 -0
  70. package/fesm/interfaces/social-auth.interface.js +1 -0
  71. package/fesm/modules/auth.module.js +8 -2
  72. package/fesm/modules/auth.module.spec.js +17 -2
  73. package/fesm/providers/facebook.social-provider.js +63 -0
  74. package/fesm/providers/google.social-provider.js +58 -0
  75. package/fesm/providers/index.js +1 -0
  76. package/fesm/providers/linkedin.social-provider.js +56 -0
  77. package/fesm/providers/microsoft.social-provider.js +68 -0
  78. package/fesm/providers/oauth-http.util.js +77 -0
  79. package/fesm/providers/social-provider.registry.js +14 -0
  80. package/fesm/providers/social-provider.registry.spec.js +131 -0
  81. package/fesm/services/auth-config.service.js +3 -0
  82. package/fesm/services/auth-config.service.spec.js +3 -0
  83. package/fesm/services/auth-datasource.provider.js +4 -0
  84. package/fesm/services/auth-datasource.provider.spec.js +30 -0
  85. package/fesm/services/authentication.service.js +38 -8
  86. package/fesm/services/authentication.service.spec.js +58 -0
  87. package/fesm/services/index.js +2 -0
  88. package/fesm/services/social-auth-config.service.js +93 -0
  89. package/fesm/services/social-auth-config.service.spec.js +67 -0
  90. package/fesm/services/social-auth.service.js +300 -0
  91. package/fesm/services/social-auth.service.spec.js +265 -0
  92. package/index.d.ts +1 -0
  93. package/interfaces/index.d.ts +1 -0
  94. package/interfaces/social-auth.interface.d.ts +43 -0
  95. package/package.json +3 -3
  96. package/providers/facebook.social-provider.d.ts +10 -0
  97. package/providers/google.social-provider.d.ts +10 -0
  98. package/providers/index.d.ts +1 -0
  99. package/providers/linkedin.social-provider.d.ts +10 -0
  100. package/providers/microsoft.social-provider.d.ts +10 -0
  101. package/providers/oauth-http.util.d.ts +10 -0
  102. package/providers/social-provider.registry.d.ts +3 -0
  103. package/services/auth-config.service.d.ts +1 -0
  104. package/services/auth-datasource.provider.d.ts +1 -0
  105. package/services/authentication.service.d.ts +4 -0
  106. package/services/index.d.ts +2 -0
  107. package/services/social-auth-config.service.d.ts +21 -0
  108. package/services/social-auth.service.d.ts +24 -0
package/README.md CHANGED
@@ -266,6 +266,73 @@ export const userEnricherProvider = {
266
266
  AuthModule.forRootAsync({ ..., providers: [userEnricherProvider] });
267
267
  ```
268
268
 
269
+ ## 5. Social Sign-In
270
+
271
+ Google, Facebook, LinkedIn and Microsoft are built in. Nothing is enabled until an
272
+ administrator saves credentials, and no flag gates the feature: the
273
+ `social_auth_config` and `user_social_account` tables are always registered, and
274
+ `SocialAuthController` / `SocialAuthConfigController` are always mounted.
275
+
276
+ ### Credentials
277
+
278
+ `POST /auth/social-config/*` is the usual CRUD surface, guarded by the
279
+ `social-config.create|read|update|delete` permissions. One row per provider is
280
+ enforced, so a provider can be addressed by name from an admin screen.
281
+
282
+ | Column | Purpose |
283
+ | -------------- | ---------------------------------------------------------------- |
284
+ | `provider` | `google` \| `facebook` \| `linkedin` \| `microsoft` (unique) |
285
+ | `isEnabled` | Whether the login page offers it; credentials survive a disable |
286
+ | `clientId` | OAuth client id from the provider's console |
287
+ | `clientSecret` | OAuth client secret |
288
+ | `scopes` | Overrides the provider's defaults when set |
289
+ | `extraConfig` | Provider extras — Microsoft's `tenantId`, for instance |
290
+
291
+ ### Sign-in flow
292
+
293
+ ```
294
+ POST /auth/social/providers { redirectUri } → [{ provider, redirectUri }]
295
+ POST /auth/social/authorize { provider, redirectUri } → { authorizationUrl, state }
296
+ browser → provider consent screen → back to redirectUri?code=…&state=…
297
+ POST /auth/social/callback { code, state, redirectUri } → the same union /auth/login returns
298
+ ```
299
+
300
+ The `state` is a single-use, cache-backed token that also records which provider
301
+ and redirect URI the sign-in started with — providers echo back nothing but
302
+ `code` and `state`, so the callback carries no provider of its own. Redeeming a
303
+ state deletes it, and a redirect URI that does not match the one the sign-in
304
+ started with is rejected.
305
+
306
+ The callback returns exactly what `POST /auth/login` returns, company selection
307
+ included, because both routes finish through `AuthenticationService.issueLoginForUser`.
308
+
309
+ ### How a user is matched
310
+
311
+ 1. The provider's own account id, via `user_social_account` — survives an email change at the provider.
312
+ 2. Otherwise the email, so an existing password account is adopted rather than duplicated.
313
+ 3. Otherwise a new account with no password.
314
+
315
+ Steps 2 and 3 require the provider to have **verified** the address. Matching on an
316
+ unverified email would let anyone who registers someone else's address at the
317
+ provider walk into their FLUSYS account, so an unverified profile is rejected.
318
+ An account already linked at step 1 is unaffected.
319
+
320
+ Step 3 — creating an account — is refused in two cases, each with its own message key:
321
+
322
+ | Condition | Result |
323
+ | ------------------------------------------- | -------------------------------------------- |
324
+ | `bootstrapAppConfig.enableSignUp` is `false` | `auth.social.account.not.linked` |
325
+ | `enableCompanyFeature` is `true` | `auth.social.company.required` |
326
+
327
+ `enableSignUp` is the single switch for social sign-up; there is no per-provider
328
+ override. With the company feature on, a brand-new account could not be given a
329
+ company and would land in a session the route guards reject, so it is refused
330
+ outright: grant the user a company permission first and their social sign-in
331
+ then works. Signing **in** is never affected by either — only account creation is.
332
+
333
+ A cache instance is required: `AuthModule` imports `CacheModule`, so this is
334
+ satisfied by default.
335
+
269
336
  ## License
270
337
 
271
338
  MIT © FLUSYS
@@ -25,6 +25,12 @@ _export(exports, {
25
25
  get SESSION_CACHE_PREFIX () {
26
26
  return SESSION_CACHE_PREFIX;
27
27
  },
28
+ get SOCIAL_STATE_PREFIX () {
29
+ return SOCIAL_STATE_PREFIX;
30
+ },
31
+ get SOCIAL_STATE_TTL_MS () {
32
+ return SOCIAL_STATE_TTL_MS;
33
+ },
28
34
  get TOKEN_REVOKED_PREFIX () {
29
35
  return TOKEN_REVOKED_PREFIX;
30
36
  },
@@ -39,3 +45,5 @@ const DEFAULT_COOKIE_NAME = 'fsn_refresh_token';
39
45
  const MAX_COOKIE_BYTES = 3500;
40
46
  const SESSION_CACHE_PREFIX = 'auth:session';
41
47
  const TOKEN_REVOKED_PREFIX = 'auth:revoked';
48
+ const SOCIAL_STATE_PREFIX = 'auth:social:state';
49
+ const SOCIAL_STATE_TTL_MS = 10 * 60 * 1000; // 10 minutes
@@ -33,13 +33,15 @@ const AUTH_EVENT_ACTIONS = {
33
33
  PASSWORD_RESET: 'password-reset',
34
34
  EMAIL_VERIFIED: 'email-verified',
35
35
  PERMISSION_GRANTED: 'permission-granted',
36
- PERMISSION_REVOKED: 'permission-revoked'
36
+ PERMISSION_REVOKED: 'permission-revoked',
37
+ SOCIAL_LOGGED_IN: 'social-logged-in'
37
38
  };
38
39
  const AUTH_EVENT_ENTITIES = {
39
40
  USER: 'appuser',
40
41
  COMPANY: 'company',
41
42
  COMPANY_BRANCH: 'company_branch',
42
43
  SESSION: 'session',
43
- USER_PERMISSION: 'user_company_permission'
44
+ USER_PERMISSION: 'user_company_permission',
45
+ SOCIAL_AUTH_CONFIG: 'social_auth_config'
44
46
  };
45
47
  const AUTH_EVENT_MODULE = 'auth';
@@ -22,6 +22,12 @@ _export(exports, {
22
22
  get EMAIL_VERIFICATION_MESSAGES () {
23
23
  return EMAIL_VERIFICATION_MESSAGES;
24
24
  },
25
+ get SOCIAL_AUTH_CONFIG_MESSAGES () {
26
+ return SOCIAL_AUTH_CONFIG_MESSAGES;
27
+ },
28
+ get SOCIAL_AUTH_MESSAGES () {
29
+ return SOCIAL_AUTH_MESSAGES;
30
+ },
25
31
  get USER_MESSAGES () {
26
32
  return USER_MESSAGES;
27
33
  },
@@ -43,7 +49,9 @@ const AUTH_MESSAGES = {
43
49
  SELECT_SUCCESS: 'auth.select.success',
44
50
  SWITCH_COMPANY_SUCCESS: 'auth.switch.company.success',
45
51
  REGISTER_EMAIL_EXISTS: 'auth.register.email.exists',
52
+ REGISTER_ACCOUNT_DELETED: 'auth.register.account.deleted',
46
53
  REGISTER_COMPANY_ALREADY_ASSIGNED: 'auth.register.company.already.assigned',
54
+ REGISTER_SIGNUP_DISABLED: 'auth.register.signup.disabled',
47
55
  PASSWORD_CHANGE_SUCCESS: 'auth.password.change.success',
48
56
  PASSWORD_CHANGE_INVALID_CURRENT: 'auth.password.change.invalid.current',
49
57
  PASSWORD_CHANGE_CURRENT_REQUIRED: 'auth.password.change.current.required',
@@ -80,6 +88,25 @@ const EMAIL_VERIFICATION_MESSAGES = {
80
88
  VERIFICATION_NOT_ENABLED: 'email.verification.not.enabled',
81
89
  RESEND_SUCCESS: 'email.verification.resend.success'
82
90
  };
91
+ const SOCIAL_AUTH_MESSAGES = {
92
+ PROVIDERS_SUCCESS: 'auth.social.providers.success',
93
+ AUTHORIZE_SUCCESS: 'auth.social.authorize.success',
94
+ LOGIN_SUCCESS: 'auth.social.login.success',
95
+ PROVIDER_DISABLED: 'auth.social.provider.disabled',
96
+ PROVIDER_NOT_CONFIGURED: 'auth.social.provider.not.configured',
97
+ STATE_INVALID: 'auth.social.state.invalid',
98
+ REDIRECT_URI_MISMATCH: 'auth.social.redirect.uri.mismatch',
99
+ EXCHANGE_FAILED: 'auth.social.exchange.failed',
100
+ PROFILE_INCOMPLETE: 'auth.social.profile.incomplete',
101
+ ACCOUNT_NOT_LINKED: 'auth.social.account.not.linked',
102
+ COMPANY_REQUIRED: 'auth.social.company.required',
103
+ EMAIL_REQUIRED: 'auth.social.email.required',
104
+ EMAIL_NOT_VERIFIED: 'auth.social.email.not.verified',
105
+ ACCOUNT_DELETED: 'auth.social.account.deleted'
106
+ };
107
+ const SOCIAL_AUTH_CONFIG_MESSAGES = {
108
+ PROVIDER_ALREADY_CONFIGURED: 'social.config.provider.already.configured'
109
+ };
83
110
  const USER_MESSAGES = {
84
111
  NOT_FOUND: 'user.not.found',
85
112
  PROFILE_UPDATE_SUCCESS: 'user.profile.update.success',
@@ -5,6 +5,8 @@ Object.defineProperty(exports, "__esModule", {
5
5
  _export_star(require("./authentication.controller"), exports);
6
6
  _export_star(require("./company-selection.controller"), exports);
7
7
  _export_star(require("./email-verification.controller"), exports);
8
+ _export_star(require("./social-auth.controller"), exports);
9
+ _export_star(require("./social-auth-config.controller"), exports);
8
10
  _export_star(require("./user.controller"), exports);
9
11
  _export_star(require("./company.controller"), exports);
10
12
  _export_star(require("./branch.controller"), exports);
@@ -0,0 +1,107 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ Object.defineProperty(exports, "SocialAuthConfigController", {
6
+ enumerable: true,
7
+ get: function() {
8
+ return SocialAuthConfigController;
9
+ }
10
+ });
11
+ const _classes = require("@flusys/nestjs-shared/classes");
12
+ const _constants = require("@flusys/nestjs-shared/constants");
13
+ const _common = require("@nestjs/common");
14
+ const _swagger = require("@nestjs/swagger");
15
+ const _socialauthdto = require("../dtos/social-auth.dto");
16
+ const _socialauthconfigservice = require("../services/social-auth-config.service");
17
+ function _define_property(obj, key, value) {
18
+ if (key in obj) {
19
+ Object.defineProperty(obj, key, {
20
+ value: value,
21
+ enumerable: true,
22
+ configurable: true,
23
+ writable: true
24
+ });
25
+ } else {
26
+ obj[key] = value;
27
+ }
28
+ return obj;
29
+ }
30
+ function _ts_decorate(decorators, target, key, desc) {
31
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
32
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
33
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
34
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
35
+ }
36
+ function _ts_metadata(k, v) {
37
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
38
+ }
39
+ function _ts_param(paramIndex, decorator) {
40
+ return function(target, key) {
41
+ decorator(target, key, paramIndex);
42
+ };
43
+ }
44
+ let SocialAuthConfigController = class SocialAuthConfigController extends (0, _classes.createApiController)(_socialauthdto.CreateSocialAuthConfigDto, _socialauthdto.UpdateSocialAuthConfigDto, _socialauthdto.SocialAuthConfigResponseDto, {
45
+ entityName: 'social.config',
46
+ security: {
47
+ insert: {
48
+ level: 'permission',
49
+ permissions: [
50
+ _constants.SOCIAL_AUTH_CONFIG_PERMISSIONS.CREATE
51
+ ]
52
+ },
53
+ bulkUpsert: {
54
+ level: 'permission',
55
+ permissions: [
56
+ _constants.SOCIAL_AUTH_CONFIG_PERMISSIONS.CREATE,
57
+ _constants.SOCIAL_AUTH_CONFIG_PERMISSIONS.UPDATE
58
+ ],
59
+ operator: 'AND'
60
+ },
61
+ getById: {
62
+ level: 'permission',
63
+ permissions: [
64
+ _constants.SOCIAL_AUTH_CONFIG_PERMISSIONS.READ
65
+ ]
66
+ },
67
+ getAll: {
68
+ level: 'permission',
69
+ permissions: [
70
+ _constants.SOCIAL_AUTH_CONFIG_PERMISSIONS.READ
71
+ ]
72
+ },
73
+ update: {
74
+ level: 'permission',
75
+ permissions: [
76
+ _constants.SOCIAL_AUTH_CONFIG_PERMISSIONS.UPDATE
77
+ ]
78
+ },
79
+ delete: {
80
+ level: 'permission',
81
+ permissions: [
82
+ _constants.SOCIAL_AUTH_CONFIG_PERMISSIONS.DELETE
83
+ ]
84
+ }
85
+ },
86
+ enabledEndpoints: [
87
+ 'insert',
88
+ 'bulkUpsert',
89
+ 'getById',
90
+ 'getAll',
91
+ 'update',
92
+ 'delete'
93
+ ]
94
+ }) {
95
+ constructor(socialAuthConfigService){
96
+ super(socialAuthConfigService), _define_property(this, "socialAuthConfigService", void 0), this.socialAuthConfigService = socialAuthConfigService;
97
+ }
98
+ };
99
+ SocialAuthConfigController = _ts_decorate([
100
+ (0, _swagger.ApiTags)('Social Auth Config'),
101
+ (0, _common.Controller)('auth/social-config'),
102
+ _ts_param(0, (0, _common.Inject)(_socialauthconfigservice.SocialAuthConfigService)),
103
+ _ts_metadata("design:type", Function),
104
+ _ts_metadata("design:paramtypes", [
105
+ typeof _socialauthconfigservice.SocialAuthConfigService === "undefined" ? Object : _socialauthconfigservice.SocialAuthConfigService
106
+ ])
107
+ ], SocialAuthConfigController);
@@ -0,0 +1,175 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ Object.defineProperty(exports, "SocialAuthController", {
6
+ enumerable: true,
7
+ get: function() {
8
+ return SocialAuthController;
9
+ }
10
+ });
11
+ const _common = require("@nestjs/common");
12
+ const _swagger = require("@nestjs/swagger");
13
+ const _throttler = require("@nestjs/throttler");
14
+ const _config = require("../config");
15
+ const _socialauthdto = require("../dtos/social-auth.dto");
16
+ const _settokeninterceptor = require("../interceptors/set-token.interceptor");
17
+ const _socialauthservice = require("../services/social-auth.service");
18
+ function _define_property(obj, key, value) {
19
+ if (key in obj) {
20
+ Object.defineProperty(obj, key, {
21
+ value: value,
22
+ enumerable: true,
23
+ configurable: true,
24
+ writable: true
25
+ });
26
+ } else {
27
+ obj[key] = value;
28
+ }
29
+ return obj;
30
+ }
31
+ function _ts_decorate(decorators, target, key, desc) {
32
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
33
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
34
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
35
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
36
+ }
37
+ function _ts_metadata(k, v) {
38
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
39
+ }
40
+ function _ts_param(paramIndex, decorator) {
41
+ return function(target, key) {
42
+ decorator(target, key, paramIndex);
43
+ };
44
+ }
45
+ let SocialAuthController = class SocialAuthController {
46
+ async getProviders(dto) {
47
+ const data = await this.socialAuthService.listEnabledProviders(dto.redirectUri);
48
+ return {
49
+ success: true,
50
+ message: 'Social providers retrieved successfully',
51
+ messageKey: _config.SOCIAL_AUTH_MESSAGES.PROVIDERS_SUCCESS,
52
+ data
53
+ };
54
+ }
55
+ async authorize(dto) {
56
+ const data = await this.socialAuthService.createAuthorization(dto.provider, dto.redirectUri);
57
+ return {
58
+ success: true,
59
+ message: 'Authorization URL generated successfully',
60
+ messageKey: _config.SOCIAL_AUTH_MESSAGES.AUTHORIZE_SUCCESS,
61
+ data
62
+ };
63
+ }
64
+ async callback(dto) {
65
+ const result = await this.socialAuthService.completeLogin(dto.code, dto.state, dto.redirectUri);
66
+ if ('requiresSelection' in result) {
67
+ return {
68
+ success: true,
69
+ message: 'Company selection required',
70
+ messageKey: _config.AUTH_MESSAGES.LOGIN_REQUIRES_SELECTION,
71
+ data: result
72
+ };
73
+ }
74
+ return {
75
+ success: true,
76
+ message: 'Login successful',
77
+ messageKey: _config.SOCIAL_AUTH_MESSAGES.LOGIN_SUCCESS,
78
+ data: result
79
+ };
80
+ }
81
+ constructor(socialAuthService){
82
+ _define_property(this, "socialAuthService", void 0);
83
+ this.socialAuthService = socialAuthService;
84
+ }
85
+ };
86
+ _ts_decorate([
87
+ (0, _common.Post)('providers'),
88
+ (0, _common.HttpCode)(_common.HttpStatus.OK),
89
+ (0, _throttler.Throttle)({
90
+ default: {
91
+ limit: 30,
92
+ ttl: 60000
93
+ }
94
+ }),
95
+ (0, _swagger.ApiOperation)({
96
+ summary: 'List enabled social sign-in providers'
97
+ }),
98
+ (0, _swagger.ApiBody)({
99
+ type: _socialauthdto.SocialProvidersDto
100
+ }),
101
+ (0, _swagger.ApiResponse)({
102
+ status: 200,
103
+ type: [
104
+ _socialauthdto.SocialProviderInfoDto
105
+ ]
106
+ }),
107
+ _ts_param(0, (0, _common.Body)()),
108
+ _ts_metadata("design:type", Function),
109
+ _ts_metadata("design:paramtypes", [
110
+ typeof _socialauthdto.SocialProvidersDto === "undefined" ? Object : _socialauthdto.SocialProvidersDto
111
+ ]),
112
+ _ts_metadata("design:returntype", Promise)
113
+ ], SocialAuthController.prototype, "getProviders", null);
114
+ _ts_decorate([
115
+ (0, _common.Post)('authorize'),
116
+ (0, _common.HttpCode)(_common.HttpStatus.OK),
117
+ (0, _throttler.Throttle)({
118
+ default: {
119
+ limit: 10,
120
+ ttl: 60000
121
+ }
122
+ }),
123
+ (0, _swagger.ApiOperation)({
124
+ summary: "Start a sign-in and get the provider's consent URL"
125
+ }),
126
+ (0, _swagger.ApiBody)({
127
+ type: _socialauthdto.SocialAuthorizeDto
128
+ }),
129
+ (0, _swagger.ApiResponse)({
130
+ status: 200,
131
+ type: _socialauthdto.SocialAuthorizeResponseDto
132
+ }),
133
+ _ts_param(0, (0, _common.Body)()),
134
+ _ts_metadata("design:type", Function),
135
+ _ts_metadata("design:paramtypes", [
136
+ typeof _socialauthdto.SocialAuthorizeDto === "undefined" ? Object : _socialauthdto.SocialAuthorizeDto
137
+ ]),
138
+ _ts_metadata("design:returntype", Promise)
139
+ ], SocialAuthController.prototype, "authorize", null);
140
+ _ts_decorate([
141
+ (0, _common.Post)('callback'),
142
+ (0, _common.HttpCode)(_common.HttpStatus.OK),
143
+ (0, _throttler.Throttle)({
144
+ default: {
145
+ limit: 10,
146
+ ttl: 60000
147
+ }
148
+ }),
149
+ (0, _common.UseInterceptors)(_settokeninterceptor.SetToken),
150
+ (0, _swagger.ApiOperation)({
151
+ summary: 'Complete a social sign-in'
152
+ }),
153
+ (0, _swagger.ApiBody)({
154
+ type: _socialauthdto.SocialCallbackDto
155
+ }),
156
+ (0, _swagger.ApiResponse)({
157
+ status: 200,
158
+ description: 'Sign-in successful, or company selection is still required'
159
+ }),
160
+ _ts_param(0, (0, _common.Body)()),
161
+ _ts_metadata("design:type", Function),
162
+ _ts_metadata("design:paramtypes", [
163
+ typeof _socialauthdto.SocialCallbackDto === "undefined" ? Object : _socialauthdto.SocialCallbackDto
164
+ ]),
165
+ _ts_metadata("design:returntype", Promise)
166
+ ], SocialAuthController.prototype, "callback", null);
167
+ SocialAuthController = _ts_decorate([
168
+ (0, _swagger.ApiTags)('Social Authentication'),
169
+ (0, _common.Controller)('auth/social'),
170
+ _ts_param(0, (0, _common.Inject)(_socialauthservice.SocialAuthService)),
171
+ _ts_metadata("design:type", Function),
172
+ _ts_metadata("design:paramtypes", [
173
+ typeof _socialauthservice.SocialAuthService === "undefined" ? Object : _socialauthservice.SocialAuthService
174
+ ])
175
+ ], SocialAuthController);
@@ -0,0 +1,79 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ const _config = require("../config");
6
+ const _socialprovidertypeenum = require("../enums/social-provider-type.enum");
7
+ const _socialauthcontroller = require("./social-auth.controller");
8
+ const REDIRECT_URI = 'http://localhost:2001/auth/social/callback';
9
+ describe('SocialAuthController', ()=>{
10
+ let controller;
11
+ let mockSocialAuthService;
12
+ beforeEach(()=>{
13
+ mockSocialAuthService = {
14
+ listEnabledProviders: jest.fn(),
15
+ createAuthorization: jest.fn(),
16
+ completeLogin: jest.fn()
17
+ };
18
+ controller = new _socialauthcontroller.SocialAuthController(mockSocialAuthService);
19
+ });
20
+ it('wraps the enabled provider list', async ()=>{
21
+ const providers = [
22
+ {
23
+ provider: _socialprovidertypeenum.SocialProviderTypeEnum.GOOGLE,
24
+ redirectUri: REDIRECT_URI
25
+ }
26
+ ];
27
+ mockSocialAuthService.listEnabledProviders.mockResolvedValue(providers);
28
+ const result = await controller.getProviders({
29
+ redirectUri: REDIRECT_URI
30
+ });
31
+ expect(result.data).toBe(providers);
32
+ expect(result.messageKey).toBe(_config.SOCIAL_AUTH_MESSAGES.PROVIDERS_SUCCESS);
33
+ expect(mockSocialAuthService.listEnabledProviders).toHaveBeenCalledWith(REDIRECT_URI);
34
+ });
35
+ it('wraps the authorization URL', async ()=>{
36
+ mockSocialAuthService.createAuthorization.mockResolvedValue({
37
+ provider: _socialprovidertypeenum.SocialProviderTypeEnum.GOOGLE,
38
+ authorizationUrl: 'https://provider.test/consent',
39
+ state: 'state-token'
40
+ });
41
+ const result = await controller.authorize({
42
+ provider: _socialprovidertypeenum.SocialProviderTypeEnum.GOOGLE,
43
+ redirectUri: REDIRECT_URI
44
+ });
45
+ expect(result.data?.authorizationUrl).toBe('https://provider.test/consent');
46
+ expect(result.messageKey).toBe(_config.SOCIAL_AUTH_MESSAGES.AUTHORIZE_SUCCESS);
47
+ });
48
+ it('wraps a completed sign-in', async ()=>{
49
+ mockSocialAuthService.completeLogin.mockResolvedValue({
50
+ accessToken: 'a',
51
+ refreshToken: 'r'
52
+ });
53
+ const result = await controller.callback({
54
+ code: 'code',
55
+ state: 'state-token',
56
+ redirectUri: REDIRECT_URI
57
+ });
58
+ expect(result.messageKey).toBe(_config.SOCIAL_AUTH_MESSAGES.LOGIN_SUCCESS);
59
+ expect(result.data).toEqual(expect.objectContaining({
60
+ accessToken: 'a'
61
+ }));
62
+ expect(mockSocialAuthService.completeLogin).toHaveBeenCalledWith('code', 'state-token', REDIRECT_URI);
63
+ });
64
+ it('wraps a sign-in that still needs a company, like the password login does', async ()=>{
65
+ mockSocialAuthService.completeLogin.mockResolvedValue({
66
+ requiresSelection: true,
67
+ sessionId: 'sess-1'
68
+ });
69
+ const result = await controller.callback({
70
+ code: 'code',
71
+ state: 'state-token',
72
+ redirectUri: REDIRECT_URI
73
+ });
74
+ expect(result.messageKey).toBe(_config.AUTH_MESSAGES.LOGIN_REQUIRES_SELECTION);
75
+ expect(result.data).toEqual(expect.objectContaining({
76
+ requiresSelection: true
77
+ }));
78
+ });
79
+ });
@@ -329,6 +329,7 @@ function buildDescription(enableCompanyFeature, enableEmailVerification, isMulti
329
329
  const features = [
330
330
  '- User registration',
331
331
  '- Login with email/password',
332
+ '- Social sign-in (Google, Facebook, LinkedIn, Microsoft)',
332
333
  '- JWT token-based authentication',
333
334
  ...enableEmailVerification ? [
334
335
  '- Email verification',
@@ -382,6 +383,13 @@ POST /auth/verify-email → { accessToken, refreshToken, user }
382
383
  - \`POST /auth/resend-verification\` - Resend verification email
383
384
  - \`POST /auth/verify-email\` - Verify email using token
384
385
  ` : '';
386
+ const socialSection = `
387
+ ### Social Sign-In Endpoints
388
+ - \`POST /auth/social/providers\` - List the providers an administrator enabled
389
+ - \`POST /auth/social/authorize\` - Start a sign-in and get the provider's consent URL
390
+ - \`POST /auth/social/callback\` - Exchange the provider's code for a session
391
+ - \`POST /auth/social-config/*\` - Manage provider credentials
392
+ `;
385
393
  const administrationSection = enableCompanyFeature ? `
386
394
  ### Administration Endpoints
387
395
  - \`POST /administration/users/*\` - User management
@@ -422,7 +430,7 @@ Handles user authentication${enableCompanyFeature ? ', company/branch management
422
430
 
423
431
  ### Features
424
432
  ${features}
425
- ${loginFlowSection}${emailVerificationSection}${administrationSection}${registrationSection}`;
433
+ ${loginFlowSection}${emailVerificationSection}${socialSection}${administrationSection}${registrationSection}`;
426
434
  }
427
435
  function authSwaggerConfig(bootstrapConfig) {
428
436
  const enableCompanyFeature = bootstrapConfig?.enableCompanyFeature ?? true;
package/cjs/dtos/index.js CHANGED
@@ -5,6 +5,7 @@ Object.defineProperty(exports, "__esModule", {
5
5
  _export_star(require("./authentication.dto"), exports);
6
6
  _export_star(require("./company-branch.dto"), exports);
7
7
  _export_star(require("./company.dto"), exports);
8
+ _export_star(require("./social-auth.dto"), exports);
8
9
  _export_star(require("./user.dto"), exports);
9
10
  _export_star(require("./user-permission.dto"), exports);
10
11
  function _export_star(from, to) {