@flusys/nestjs-auth 7.0.2 → 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
@@ -0,0 +1,68 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ Object.defineProperty(exports, "googleSocialProvider", {
6
+ enumerable: true,
7
+ get: function() {
8
+ return googleSocialProvider;
9
+ }
10
+ });
11
+ const _socialprovidertypeenum = require("../enums/social-provider-type.enum");
12
+ const _oauthhttputil = require("./oauth-http.util");
13
+ function _define_property(obj, key, value) {
14
+ if (key in obj) {
15
+ Object.defineProperty(obj, key, {
16
+ value: value,
17
+ enumerable: true,
18
+ configurable: true,
19
+ writable: true
20
+ });
21
+ } else {
22
+ obj[key] = value;
23
+ }
24
+ return obj;
25
+ }
26
+ const AUTHORIZE_URL = 'https://accounts.google.com/o/oauth2/v2/auth';
27
+ const TOKEN_URL = 'https://oauth2.googleapis.com/token';
28
+ const USERINFO_URL = 'https://openidconnect.googleapis.com/v1/userinfo';
29
+ let GoogleSocialProvider = class GoogleSocialProvider {
30
+ buildAuthorizationUrl(credentials, state) {
31
+ const query = (0, _oauthhttputil.buildQuery)({
32
+ client_id: credentials.clientId,
33
+ redirect_uri: credentials.redirectUri,
34
+ response_type: 'code',
35
+ scope: (0, _oauthhttputil.resolveScopes)(credentials, this.defaultScopes),
36
+ state,
37
+ access_type: 'online',
38
+ prompt: 'select_account'
39
+ });
40
+ return `${AUTHORIZE_URL}?${query}`;
41
+ }
42
+ async fetchProfile(credentials, code) {
43
+ const token = await (0, _oauthhttputil.postForm)(this.provider, TOKEN_URL, {
44
+ code,
45
+ client_id: credentials.clientId,
46
+ client_secret: credentials.clientSecret,
47
+ redirect_uri: credentials.redirectUri,
48
+ grant_type: 'authorization_code'
49
+ });
50
+ const profile = await (0, _oauthhttputil.getJson)(this.provider, USERINFO_URL, (0, _oauthhttputil.readAccessToken)(this.provider, token));
51
+ return {
52
+ providerUserId: (0, _oauthhttputil.readString)(profile, 'sub') ?? '',
53
+ email: (0, _oauthhttputil.readString)(profile, 'email'),
54
+ emailVerified: profile['email_verified'] === true,
55
+ name: (0, _oauthhttputil.readString)(profile, 'name'),
56
+ avatarUrl: (0, _oauthhttputil.readString)(profile, 'picture')
57
+ };
58
+ }
59
+ constructor(){
60
+ _define_property(this, "provider", _socialprovidertypeenum.SocialProviderTypeEnum.GOOGLE);
61
+ _define_property(this, "defaultScopes", [
62
+ 'openid',
63
+ 'email',
64
+ 'profile'
65
+ ]);
66
+ }
67
+ };
68
+ const googleSocialProvider = new GoogleSocialProvider();
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ _export_star(require("./social-provider.registry"), exports);
6
+ function _export_star(from, to) {
7
+ Object.keys(from).forEach(function(k) {
8
+ if (k !== "default" && !Object.prototype.hasOwnProperty.call(to, k)) {
9
+ Object.defineProperty(to, k, {
10
+ enumerable: true,
11
+ get: function() {
12
+ return from[k];
13
+ }
14
+ });
15
+ }
16
+ });
17
+ return from;
18
+ }
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ Object.defineProperty(exports, "linkedInSocialProvider", {
6
+ enumerable: true,
7
+ get: function() {
8
+ return linkedInSocialProvider;
9
+ }
10
+ });
11
+ const _socialprovidertypeenum = require("../enums/social-provider-type.enum");
12
+ const _oauthhttputil = require("./oauth-http.util");
13
+ function _define_property(obj, key, value) {
14
+ if (key in obj) {
15
+ Object.defineProperty(obj, key, {
16
+ value: value,
17
+ enumerable: true,
18
+ configurable: true,
19
+ writable: true
20
+ });
21
+ } else {
22
+ obj[key] = value;
23
+ }
24
+ return obj;
25
+ }
26
+ const AUTHORIZE_URL = 'https://www.linkedin.com/oauth/v2/authorization';
27
+ const TOKEN_URL = 'https://www.linkedin.com/oauth/v2/accessToken';
28
+ const USERINFO_URL = 'https://api.linkedin.com/v2/userinfo';
29
+ let LinkedInSocialProvider = class LinkedInSocialProvider {
30
+ buildAuthorizationUrl(credentials, state) {
31
+ const query = (0, _oauthhttputil.buildQuery)({
32
+ response_type: 'code',
33
+ client_id: credentials.clientId,
34
+ redirect_uri: credentials.redirectUri,
35
+ scope: (0, _oauthhttputil.resolveScopes)(credentials, this.defaultScopes),
36
+ state
37
+ });
38
+ return `${AUTHORIZE_URL}?${query}`;
39
+ }
40
+ async fetchProfile(credentials, code) {
41
+ const token = await (0, _oauthhttputil.postForm)(this.provider, TOKEN_URL, {
42
+ grant_type: 'authorization_code',
43
+ code,
44
+ client_id: credentials.clientId,
45
+ client_secret: credentials.clientSecret,
46
+ redirect_uri: credentials.redirectUri
47
+ });
48
+ const profile = await (0, _oauthhttputil.getJson)(this.provider, USERINFO_URL, (0, _oauthhttputil.readAccessToken)(this.provider, token));
49
+ return {
50
+ providerUserId: (0, _oauthhttputil.readString)(profile, 'sub') ?? '',
51
+ email: (0, _oauthhttputil.readString)(profile, 'email'),
52
+ emailVerified: profile['email_verified'] === true,
53
+ name: (0, _oauthhttputil.readString)(profile, 'name'),
54
+ avatarUrl: (0, _oauthhttputil.readString)(profile, 'picture')
55
+ };
56
+ }
57
+ constructor(){
58
+ _define_property(this, "provider", _socialprovidertypeenum.SocialProviderTypeEnum.LINKEDIN);
59
+ _define_property(this, "defaultScopes", [
60
+ 'openid',
61
+ 'profile',
62
+ 'email'
63
+ ]);
64
+ }
65
+ };
66
+ const linkedInSocialProvider = new LinkedInSocialProvider();
@@ -0,0 +1,78 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ Object.defineProperty(exports, "microsoftSocialProvider", {
6
+ enumerable: true,
7
+ get: function() {
8
+ return microsoftSocialProvider;
9
+ }
10
+ });
11
+ const _socialprovidertypeenum = require("../enums/social-provider-type.enum");
12
+ const _oauthhttputil = require("./oauth-http.util");
13
+ function _define_property(obj, key, value) {
14
+ if (key in obj) {
15
+ Object.defineProperty(obj, key, {
16
+ value: value,
17
+ enumerable: true,
18
+ configurable: true,
19
+ writable: true
20
+ });
21
+ } else {
22
+ obj[key] = value;
23
+ }
24
+ return obj;
25
+ }
26
+ const GRAPH_ME_URL = 'https://graph.microsoft.com/v1.0/me';
27
+ const DEFAULT_TENANT = 'common';
28
+ function resolveTenant(credentials) {
29
+ const tenantId = credentials.extraConfig?.['tenantId'];
30
+ return typeof tenantId === 'string' && tenantId ? tenantId : DEFAULT_TENANT;
31
+ }
32
+ function endpoint(credentials, action) {
33
+ return `https://login.microsoftonline.com/${resolveTenant(credentials)}/oauth2/v2.0/${action}`;
34
+ }
35
+ let MicrosoftSocialProvider = class MicrosoftSocialProvider {
36
+ buildAuthorizationUrl(credentials, state) {
37
+ const query = (0, _oauthhttputil.buildQuery)({
38
+ client_id: credentials.clientId,
39
+ response_type: 'code',
40
+ redirect_uri: credentials.redirectUri,
41
+ response_mode: 'query',
42
+ scope: (0, _oauthhttputil.resolveScopes)(credentials, this.defaultScopes),
43
+ state
44
+ });
45
+ return `${endpoint(credentials, 'authorize')}?${query}`;
46
+ }
47
+ async fetchProfile(credentials, code) {
48
+ const token = await (0, _oauthhttputil.postForm)(this.provider, endpoint(credentials, 'token'), {
49
+ client_id: credentials.clientId,
50
+ client_secret: credentials.clientSecret,
51
+ code,
52
+ redirect_uri: credentials.redirectUri,
53
+ grant_type: 'authorization_code',
54
+ scope: (0, _oauthhttputil.resolveScopes)(credentials, this.defaultScopes)
55
+ });
56
+ const profile = await (0, _oauthhttputil.getJson)(this.provider, GRAPH_ME_URL, (0, _oauthhttputil.readAccessToken)(this.provider, token));
57
+ // `mail` is Microsoft's confirmed mailbox claim; `userPrincipalName` is a sign-in
58
+ // identifier that is not guaranteed to be a verified, owned mailbox address.
59
+ const verifiedEmail = (0, _oauthhttputil.readString)(profile, 'mail');
60
+ return {
61
+ providerUserId: (0, _oauthhttputil.readString)(profile, 'id') ?? '',
62
+ email: verifiedEmail ?? (0, _oauthhttputil.readString)(profile, 'userPrincipalName'),
63
+ emailVerified: !!verifiedEmail,
64
+ name: (0, _oauthhttputil.readString)(profile, 'displayName'),
65
+ avatarUrl: null
66
+ };
67
+ }
68
+ constructor(){
69
+ _define_property(this, "provider", _socialprovidertypeenum.SocialProviderTypeEnum.MICROSOFT);
70
+ _define_property(this, "defaultScopes", [
71
+ 'openid',
72
+ 'profile',
73
+ 'email',
74
+ 'User.Read'
75
+ ]);
76
+ }
77
+ };
78
+ const microsoftSocialProvider = new MicrosoftSocialProvider();
@@ -0,0 +1,107 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ function _export(target, all) {
6
+ for(var name in all)Object.defineProperty(target, name, {
7
+ enumerable: true,
8
+ get: Object.getOwnPropertyDescriptor(all, name).get
9
+ });
10
+ }
11
+ _export(exports, {
12
+ get buildQuery () {
13
+ return buildQuery;
14
+ },
15
+ get getJson () {
16
+ return getJson;
17
+ },
18
+ get postForm () {
19
+ return postForm;
20
+ },
21
+ get readAccessToken () {
22
+ return readAccessToken;
23
+ },
24
+ get readString () {
25
+ return readString;
26
+ },
27
+ get resolveScopes () {
28
+ return resolveScopes;
29
+ }
30
+ });
31
+ const _common = require("@nestjs/common");
32
+ const _messagekeys = require("../config/message-keys");
33
+ const OAUTH_HTTP_TIMEOUT_MS = 10_000;
34
+ function failExchange(provider, detail) {
35
+ throw new _common.UnauthorizedException({
36
+ message: `Social sign-in with ${provider} failed: ${detail}`,
37
+ messageKey: _messagekeys.SOCIAL_AUTH_MESSAGES.EXCHANGE_FAILED,
38
+ messageVariables: {
39
+ provider
40
+ }
41
+ });
42
+ }
43
+ async function fetchWithTimeout(provider, url, init) {
44
+ try {
45
+ return await fetch(url, {
46
+ ...init,
47
+ signal: AbortSignal.timeout(OAUTH_HTTP_TIMEOUT_MS)
48
+ });
49
+ } catch (error) {
50
+ if (error instanceof Error && error.name === 'TimeoutError') {
51
+ failExchange(provider, 'request timed out');
52
+ }
53
+ throw error;
54
+ }
55
+ }
56
+ async function readJson(provider, response) {
57
+ const body = await response.text();
58
+ if (!response.ok) {
59
+ failExchange(provider, `${response.status} ${body.slice(0, 200)}`);
60
+ }
61
+ try {
62
+ return JSON.parse(body);
63
+ } catch {
64
+ return failExchange(provider, 'response was not JSON');
65
+ }
66
+ }
67
+ async function postForm(provider, url, form) {
68
+ const response = await fetchWithTimeout(provider, url, {
69
+ method: 'POST',
70
+ headers: {
71
+ 'Content-Type': 'application/x-www-form-urlencoded',
72
+ Accept: 'application/json'
73
+ },
74
+ body: new URLSearchParams(form).toString()
75
+ });
76
+ return readJson(provider, response);
77
+ }
78
+ async function getJson(provider, url, accessToken) {
79
+ const response = await fetchWithTimeout(provider, url, {
80
+ headers: {
81
+ Authorization: `Bearer ${accessToken}`,
82
+ Accept: 'application/json'
83
+ }
84
+ });
85
+ return readJson(provider, response);
86
+ }
87
+ function readAccessToken(provider, token) {
88
+ const accessToken = token['access_token'];
89
+ if (typeof accessToken !== 'string' || !accessToken) {
90
+ failExchange(provider, 'no access token returned');
91
+ }
92
+ return accessToken;
93
+ }
94
+ function readString(source, key) {
95
+ const value = source[key];
96
+ return typeof value === 'string' && value ? value : null;
97
+ }
98
+ function buildQuery(params) {
99
+ const query = new URLSearchParams();
100
+ for (const [key, value] of Object.entries(params)){
101
+ if (value) query.append(key, value);
102
+ }
103
+ return query.toString();
104
+ }
105
+ function resolveScopes(credentials, defaults, separator = ' ') {
106
+ return (credentials.scopes?.length ? credentials.scopes : defaults).join(separator);
107
+ }
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ Object.defineProperty(exports, "getSocialProvider", {
6
+ enumerable: true,
7
+ get: function() {
8
+ return getSocialProvider;
9
+ }
10
+ });
11
+ const _socialprovidertypeenum = require("../enums/social-provider-type.enum");
12
+ const _facebooksocialprovider = require("./facebook.social-provider");
13
+ const _googlesocialprovider = require("./google.social-provider");
14
+ const _linkedinsocialprovider = require("./linkedin.social-provider");
15
+ const _microsoftsocialprovider = require("./microsoft.social-provider");
16
+ const PROVIDERS = {
17
+ [_socialprovidertypeenum.SocialProviderTypeEnum.GOOGLE]: _googlesocialprovider.googleSocialProvider,
18
+ [_socialprovidertypeenum.SocialProviderTypeEnum.FACEBOOK]: _facebooksocialprovider.facebookSocialProvider,
19
+ [_socialprovidertypeenum.SocialProviderTypeEnum.LINKEDIN]: _linkedinsocialprovider.linkedInSocialProvider,
20
+ [_socialprovidertypeenum.SocialProviderTypeEnum.MICROSOFT]: _microsoftsocialprovider.microsoftSocialProvider
21
+ };
22
+ function getSocialProvider(provider) {
23
+ return PROVIDERS[provider];
24
+ }
@@ -0,0 +1,135 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ const _common = require("@nestjs/common");
6
+ const _socialprovidertypeenum = require("../enums/social-provider-type.enum");
7
+ const _socialproviderregistry = require("./social-provider.registry");
8
+ const CREDENTIALS = {
9
+ clientId: 'client-id',
10
+ clientSecret: 'client-secret',
11
+ redirectUri: 'http://localhost:2001/auth/social/callback'
12
+ };
13
+ function mockFetchSequence(...responses) {
14
+ const fetchMock = jest.fn();
15
+ for (const response of responses){
16
+ fetchMock.mockResolvedValueOnce({
17
+ ok: response.ok ?? true,
18
+ status: response.status ?? 200,
19
+ text: async ()=>JSON.stringify(response.body)
20
+ });
21
+ }
22
+ globalThis.fetch = fetchMock;
23
+ return fetchMock;
24
+ }
25
+ describe('social providers', ()=>{
26
+ afterEach(()=>jest.restoreAllMocks());
27
+ describe('buildAuthorizationUrl', ()=>{
28
+ it.each(Object.values(_socialprovidertypeenum.SocialProviderTypeEnum))('carries the client id, redirect URI and state for %s', (provider)=>{
29
+ const url = new URL((0, _socialproviderregistry.getSocialProvider)(provider).buildAuthorizationUrl(CREDENTIALS, 'state-token'));
30
+ expect(url.searchParams.get('client_id')).toBe('client-id');
31
+ expect(url.searchParams.get('redirect_uri')).toBe(CREDENTIALS.redirectUri);
32
+ expect(url.searchParams.get('state')).toBe('state-token');
33
+ expect(url.searchParams.get('scope')).toBeTruthy();
34
+ });
35
+ it('uses the configured scopes over the provider defaults', ()=>{
36
+ const url = new URL((0, _socialproviderregistry.getSocialProvider)(_socialprovidertypeenum.SocialProviderTypeEnum.GOOGLE).buildAuthorizationUrl({
37
+ ...CREDENTIALS,
38
+ scopes: [
39
+ 'openid',
40
+ 'email'
41
+ ]
42
+ }, 'state-token'));
43
+ expect(url.searchParams.get('scope')).toBe('openid email');
44
+ });
45
+ it('targets the common Microsoft directory unless a tenant is configured', ()=>{
46
+ const provider = (0, _socialproviderregistry.getSocialProvider)(_socialprovidertypeenum.SocialProviderTypeEnum.MICROSOFT);
47
+ expect(provider.buildAuthorizationUrl(CREDENTIALS, 'state-token')).toContain('login.microsoftonline.com/common/');
48
+ expect(provider.buildAuthorizationUrl({
49
+ ...CREDENTIALS,
50
+ extraConfig: {
51
+ tenantId: 'contoso'
52
+ }
53
+ }, 'state-token')).toContain('login.microsoftonline.com/contoso/');
54
+ });
55
+ });
56
+ describe('fetchProfile', ()=>{
57
+ it('normalises a Google OpenID profile', async ()=>{
58
+ mockFetchSequence({
59
+ body: {
60
+ access_token: 'token'
61
+ }
62
+ }, {
63
+ body: {
64
+ sub: 'google-1',
65
+ email: 'user@example.com',
66
+ email_verified: true,
67
+ name: 'Test User',
68
+ picture: 'https://cdn.test/avatar.png'
69
+ }
70
+ });
71
+ const profile = await (0, _socialproviderregistry.getSocialProvider)(_socialprovidertypeenum.SocialProviderTypeEnum.GOOGLE).fetchProfile(CREDENTIALS, 'code');
72
+ expect(profile).toEqual({
73
+ providerUserId: 'google-1',
74
+ email: 'user@example.com',
75
+ emailVerified: true,
76
+ name: 'Test User',
77
+ avatarUrl: 'https://cdn.test/avatar.png'
78
+ });
79
+ });
80
+ it('reads the nested avatar Facebook returns', async ()=>{
81
+ mockFetchSequence({
82
+ body: {
83
+ access_token: 'token'
84
+ }
85
+ }, {
86
+ body: {
87
+ id: 'fb-1',
88
+ name: 'Test User',
89
+ email: 'user@example.com',
90
+ picture: {
91
+ data: {
92
+ url: 'https://cdn.test/fb.png'
93
+ }
94
+ }
95
+ }
96
+ });
97
+ const profile = await (0, _socialproviderregistry.getSocialProvider)(_socialprovidertypeenum.SocialProviderTypeEnum.FACEBOOK).fetchProfile(CREDENTIALS, 'code');
98
+ expect(profile.avatarUrl).toBe('https://cdn.test/fb.png');
99
+ expect(profile.emailVerified).toBe(true);
100
+ });
101
+ it('falls back to the Microsoft principal name when no mailbox is set', async ()=>{
102
+ mockFetchSequence({
103
+ body: {
104
+ access_token: 'token'
105
+ }
106
+ }, {
107
+ body: {
108
+ id: 'ms-1',
109
+ displayName: 'Test User',
110
+ userPrincipalName: 'user@contoso.com'
111
+ }
112
+ });
113
+ const profile = await (0, _socialproviderregistry.getSocialProvider)(_socialprovidertypeenum.SocialProviderTypeEnum.MICROSOFT).fetchProfile(CREDENTIALS, 'code');
114
+ expect(profile.email).toBe('user@contoso.com');
115
+ });
116
+ it('rejects when the token endpoint fails', async ()=>{
117
+ mockFetchSequence({
118
+ ok: false,
119
+ status: 400,
120
+ body: {
121
+ error: 'invalid_grant'
122
+ }
123
+ });
124
+ await expect((0, _socialproviderregistry.getSocialProvider)(_socialprovidertypeenum.SocialProviderTypeEnum.LINKEDIN).fetchProfile(CREDENTIALS, 'code')).rejects.toThrow(_common.UnauthorizedException);
125
+ });
126
+ it('rejects when the token response carries no access token', async ()=>{
127
+ mockFetchSequence({
128
+ body: {
129
+ token_type: 'bearer'
130
+ }
131
+ });
132
+ await expect((0, _socialproviderregistry.getSocialProvider)(_socialprovidertypeenum.SocialProviderTypeEnum.GOOGLE).fetchProfile(CREDENTIALS, 'code')).rejects.toThrow(_common.UnauthorizedException);
133
+ });
134
+ });
135
+ });
@@ -55,6 +55,9 @@ let AuthConfigService = class AuthConfigService {
55
55
  isEmailVerificationEnabled() {
56
56
  return this.options.bootstrapAppConfig?.enableEmailVerification ?? true;
57
57
  }
58
+ isSignUpEnabled() {
59
+ return this.options.bootstrapAppConfig?.enableSignUp ?? true;
60
+ }
58
61
  isPermissionModeFound() {
59
62
  return this.options.bootstrapAppConfig?.permissionMode !== undefined;
60
63
  }
@@ -21,6 +21,7 @@ describe('AuthConfigService', ()=>{
21
21
  expect(service.isMultiTenant()).toBe(false);
22
22
  expect(service.isCompanyFeatureEnabled()).toBe(false);
23
23
  expect(service.isEmailVerificationEnabled()).toBe(true);
24
+ expect(service.isSignUpEnabled()).toBe(true);
24
25
  expect(service.isPermissionModeFound()).toBe(false);
25
26
  });
26
27
  it('throws when reading the JWT secret without configuration', ()=>{
@@ -53,6 +54,7 @@ describe('AuthConfigService', ()=>{
53
54
  databaseMode: 'multi-tenant',
54
55
  enableCompanyFeature: true,
55
56
  enableEmailVerification: false,
57
+ enableSignUp: false,
56
58
  permissionMode: 'RBAC'
57
59
  },
58
60
  config: {
@@ -74,6 +76,7 @@ describe('AuthConfigService', ()=>{
74
76
  const service = new _authconfigservice.AuthConfigService(buildOptions());
75
77
  expect(service.isCompanyFeatureEnabled()).toBe(true);
76
78
  expect(service.isEmailVerificationEnabled()).toBe(false);
79
+ expect(service.isSignUpEnabled()).toBe(false);
77
80
  expect(service.isPermissionModeFound()).toBe(true);
78
81
  });
79
82
  it('returns configured JWT and refresh token values', ()=>{
@@ -99,6 +99,10 @@ let AuthDataSourceProvider = class AuthDataSourceProvider extends _nestjsshared.
99
99
  const effectiveTenant = tenant ?? this.getCurrentTenant();
100
100
  return effectiveTenant?.enableEmailVerification ?? this.authOptions.bootstrapAppConfig?.enableEmailVerification ?? true;
101
101
  }
102
+ isSignUpEnabled(tenant) {
103
+ const effectiveTenant = tenant ?? this.getCurrentTenant();
104
+ return effectiveTenant?.enableSignUp ?? this.authOptions.bootstrapAppConfig?.enableSignUp ?? true;
105
+ }
102
106
  async getAuthEntities() {
103
107
  const { getAuthEntitiesByConfig } = await Promise.resolve().then(()=>/*#__PURE__*/ _interop_require_wildcard(require("../entities")));
104
108
  return getAuthEntitiesByConfig({
@@ -82,6 +82,36 @@ describe('AuthDataSourceProvider', ()=>{
82
82
  })).toBe(false);
83
83
  });
84
84
  });
85
+ describe('isSignUpEnabled', ()=>{
86
+ it('returns true by default (sign-up defaults to enabled)', ()=>{
87
+ const provider = new _authdatasourceprovider.AuthDataSourceProvider(buildOptions({
88
+ bootstrapAppConfig: undefined
89
+ }));
90
+ expect(provider.isSignUpEnabled()).toBe(true);
91
+ });
92
+ it('returns false when bootstrap config disables it', ()=>{
93
+ const provider = new _authdatasourceprovider.AuthDataSourceProvider(buildOptions({
94
+ bootstrapAppConfig: {
95
+ databaseMode: 'single',
96
+ enableCompanyFeature: false,
97
+ enableSignUp: false
98
+ }
99
+ }));
100
+ expect(provider.isSignUpEnabled()).toBe(false);
101
+ });
102
+ it('prefers the tenant override over bootstrap config', ()=>{
103
+ const provider = new _authdatasourceprovider.AuthDataSourceProvider(buildOptions({
104
+ bootstrapAppConfig: {
105
+ databaseMode: 'multi-tenant',
106
+ enableCompanyFeature: true,
107
+ enableSignUp: true
108
+ }
109
+ }));
110
+ expect(provider.isSignUpEnabled({
111
+ enableSignUp: false
112
+ })).toBe(false);
113
+ });
114
+ });
85
115
  describe('getAuthEntities', ()=>{
86
116
  it('returns only base entities when company feature and email verification are disabled', async ()=>{
87
117
  const provider = new _authdatasourceprovider.AuthDataSourceProvider(buildOptions({
@@ -108,6 +108,9 @@ let AuthenticationService = class AuthenticationService {
108
108
  const providerAvailable = this.authEmailService?.isEmailEnabled() ?? false;
109
109
  return featureEnabled && providerAvailable;
110
110
  }
111
+ isSignUpEnabled() {
112
+ return this.dataSourceProvider?.isSignUpEnabled() ?? this.authConfig.isSignUpEnabled();
113
+ }
111
114
  async getUserRepository() {
112
115
  if (!this.dataSourceProvider) {
113
116
  throw new _common.InternalServerErrorException({
@@ -134,12 +137,7 @@ let AuthenticationService = class AuthenticationService {
134
137
  };
135
138
  }
136
139
  const user = await this.validateUser(email, password);
137
- if (!user.isActive) {
138
- throw new _common.ForbiddenException({
139
- message: 'User account is deactivated',
140
- messageKey: _config.AUTH_MESSAGES.LOGIN_ACCOUNT_DEACTIVATED
141
- });
142
- }
140
+ this.ensureUserActive(user);
143
141
  if (this.isEmailVerificationEnabled() && !user.emailVerified) {
144
142
  return {
145
143
  requiresEmailVerification: true,
@@ -148,11 +146,23 @@ let AuthenticationService = class AuthenticationService {
148
146
  messageKey: _config.AUTH_MESSAGES.LOGIN_EMAIL_NOT_VERIFIED
149
147
  };
150
148
  }
149
+ return this.issueLoginForUser(user);
150
+ }
151
+ async issueLoginForUser(user) {
152
+ this.ensureUserActive(user);
151
153
  if (!this.isCompanyFeatureEnabled()) {
152
154
  return this.generateLoginResponse(user);
153
155
  }
154
156
  return await this.handleCompanyBasedLogin(user);
155
157
  }
158
+ ensureUserActive(user) {
159
+ if (!user.isActive) {
160
+ throw new _common.ForbiddenException({
161
+ message: 'User account is deactivated',
162
+ messageKey: _config.AUTH_MESSAGES.LOGIN_ACCOUNT_DEACTIVATED
163
+ });
164
+ }
165
+ }
156
166
  async handleCompanyBasedLogin(user) {
157
167
  const permissionRepository = await this.getCompanyScopedRepository(_usercompanypermissionentity.UserCompanyPermission);
158
168
  const branchRepository = await this.getCompanyScopedRepository(_companybranchentity.CompanyBranch);
@@ -200,8 +210,15 @@ let AuthenticationService = class AuthenticationService {
200
210
  const existingUser = await userRepository.findOne({
201
211
  where: {
202
212
  email: dto.email
203
- }
213
+ },
214
+ withDeleted: true
204
215
  });
216
+ if (existingUser?.deletedAt) {
217
+ throw new _common.BadRequestException({
218
+ message: 'This account has been permanently deleted. Please contact your system administrator.',
219
+ messageKey: _config.AUTH_MESSAGES.REGISTER_ACCOUNT_DELETED
220
+ });
221
+ }
205
222
  if (existingUser && this.isCompanyFeatureEnabled()) {
206
223
  return await this.handleExistingUserCompanyRegistration(existingUser, dto);
207
224
  }
@@ -211,6 +228,12 @@ let AuthenticationService = class AuthenticationService {
211
228
  messageKey: _config.AUTH_MESSAGES.REGISTER_EMAIL_EXISTS
212
229
  });
213
230
  }
231
+ if (!this.isSignUpEnabled()) {
232
+ throw new _common.BadRequestException({
233
+ message: 'New account registration is currently disabled.',
234
+ messageKey: _config.AUTH_MESSAGES.REGISTER_SIGNUP_DISABLED
235
+ });
236
+ }
214
237
  if (this.isEmailVerificationEnabled() && verifyUrlBase) {
215
238
  const companyData = this.isCompanyFeatureEnabled() ? {
216
239
  companySlug: dto.companySlug,
@@ -338,8 +361,15 @@ let AuthenticationService = class AuthenticationService {
338
361
  const existingUser = await queryRunner.manager.findOne(_appuserentity.AppUser, {
339
362
  where: {
340
363
  email: pending.email
341
- }
364
+ },
365
+ withDeleted: true
342
366
  });
367
+ if (existingUser?.deletedAt) {
368
+ throw new _common.BadRequestException({
369
+ message: 'This account has been permanently deleted. Please contact your system administrator.',
370
+ messageKey: _config.AUTH_MESSAGES.REGISTER_ACCOUNT_DELETED
371
+ });
372
+ }
343
373
  if (existingUser) {
344
374
  throw new _common.BadRequestException({
345
375
  message: 'This email has already been registered.',