@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.
- package/README.md +67 -0
- package/cjs/config/auth.constants.js +8 -0
- package/cjs/config/event-actions.js +4 -2
- package/cjs/config/message-keys.js +27 -0
- package/cjs/controllers/index.js +2 -0
- package/cjs/controllers/social-auth-config.controller.js +107 -0
- package/cjs/controllers/social-auth.controller.js +175 -0
- package/cjs/controllers/social-auth.controller.spec.js +79 -0
- package/cjs/docs/auth-swagger.config.js +9 -1
- package/cjs/dtos/index.js +1 -0
- package/cjs/dtos/social-auth.dto.js +318 -0
- package/cjs/entities/index.js +3 -1
- package/cjs/entities/index.spec.js +12 -1
- package/cjs/entities/social-auth-config.entity.js +102 -0
- package/cjs/enums/index.js +1 -0
- package/cjs/enums/social-provider-type.enum.js +17 -0
- package/cjs/index.js +1 -0
- package/cjs/interfaces/index.js +1 -0
- package/cjs/interfaces/social-auth.interface.js +4 -0
- package/cjs/modules/auth.module.js +6 -0
- package/cjs/modules/auth.module.spec.js +15 -0
- package/cjs/providers/facebook.social-provider.js +73 -0
- package/cjs/providers/google.social-provider.js +68 -0
- package/cjs/providers/index.js +18 -0
- package/cjs/providers/linkedin.social-provider.js +66 -0
- package/cjs/providers/microsoft.social-provider.js +78 -0
- package/cjs/providers/oauth-http.util.js +107 -0
- package/cjs/providers/social-provider.registry.js +24 -0
- package/cjs/providers/social-provider.registry.spec.js +135 -0
- package/cjs/services/auth-config.service.js +3 -0
- package/cjs/services/auth-config.service.spec.js +3 -0
- package/cjs/services/auth-datasource.provider.js +4 -0
- package/cjs/services/auth-datasource.provider.spec.js +30 -0
- package/cjs/services/authentication.service.js +38 -8
- package/cjs/services/authentication.service.spec.js +58 -0
- package/cjs/services/index.js +2 -0
- package/cjs/services/social-auth-config.service.js +103 -0
- package/cjs/services/social-auth-config.service.spec.js +71 -0
- package/cjs/services/social-auth.service.js +310 -0
- package/cjs/services/social-auth.service.spec.js +310 -0
- package/config/auth.constants.d.ts +2 -0
- package/config/event-actions.d.ts +2 -0
- package/config/message-keys.d.ts +21 -0
- package/controllers/index.d.ts +2 -0
- package/controllers/social-auth-config.controller.d.ts +22 -0
- package/controllers/social-auth.controller.d.ts +11 -0
- package/dtos/index.d.ts +1 -0
- package/dtos/social-auth.dto.d.ts +42 -0
- package/entities/index.d.ts +3 -1
- package/entities/social-auth-config.entity.d.ts +9 -0
- package/enums/index.d.ts +1 -0
- package/enums/social-provider-type.enum.d.ts +6 -0
- package/fesm/config/auth.constants.js +3 -0
- package/fesm/config/event-actions.js +4 -2
- package/fesm/config/message-keys.js +21 -0
- package/fesm/controllers/index.js +2 -0
- package/fesm/controllers/social-auth-config.controller.js +97 -0
- package/fesm/controllers/social-auth.controller.js +165 -0
- package/fesm/controllers/social-auth.controller.spec.js +75 -0
- package/fesm/docs/auth-swagger.config.js +9 -1
- package/fesm/dtos/index.js +1 -0
- package/fesm/dtos/social-auth.dto.js +282 -0
- package/fesm/entities/index.js +4 -1
- package/fesm/entities/index.spec.js +12 -1
- package/fesm/entities/social-auth-config.entity.js +92 -0
- package/fesm/enums/index.js +1 -0
- package/fesm/enums/social-provider-type.enum.js +7 -0
- package/fesm/index.js +1 -0
- package/fesm/interfaces/index.js +1 -0
- package/fesm/interfaces/social-auth.interface.js +1 -0
- package/fesm/modules/auth.module.js +8 -2
- package/fesm/modules/auth.module.spec.js +17 -2
- package/fesm/providers/facebook.social-provider.js +63 -0
- package/fesm/providers/google.social-provider.js +58 -0
- package/fesm/providers/index.js +1 -0
- package/fesm/providers/linkedin.social-provider.js +56 -0
- package/fesm/providers/microsoft.social-provider.js +68 -0
- package/fesm/providers/oauth-http.util.js +77 -0
- package/fesm/providers/social-provider.registry.js +14 -0
- package/fesm/providers/social-provider.registry.spec.js +131 -0
- package/fesm/services/auth-config.service.js +3 -0
- package/fesm/services/auth-config.service.spec.js +3 -0
- package/fesm/services/auth-datasource.provider.js +4 -0
- package/fesm/services/auth-datasource.provider.spec.js +30 -0
- package/fesm/services/authentication.service.js +38 -8
- package/fesm/services/authentication.service.spec.js +58 -0
- package/fesm/services/index.js +2 -0
- package/fesm/services/social-auth-config.service.js +93 -0
- package/fesm/services/social-auth-config.service.spec.js +67 -0
- package/fesm/services/social-auth.service.js +300 -0
- package/fesm/services/social-auth.service.spec.js +265 -0
- package/index.d.ts +1 -0
- package/interfaces/index.d.ts +1 -0
- package/interfaces/social-auth.interface.d.ts +43 -0
- package/package.json +3 -3
- package/providers/facebook.social-provider.d.ts +10 -0
- package/providers/google.social-provider.d.ts +10 -0
- package/providers/index.d.ts +1 -0
- package/providers/linkedin.social-provider.d.ts +10 -0
- package/providers/microsoft.social-provider.d.ts +10 -0
- package/providers/oauth-http.util.d.ts +10 -0
- package/providers/social-provider.registry.d.ts +3 -0
- package/services/auth-config.service.d.ts +1 -0
- package/services/auth-datasource.provider.d.ts +1 -0
- package/services/authentication.service.d.ts +4 -0
- package/services/index.d.ts +2 -0
- package/services/social-auth-config.service.d.ts +21 -0
- 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',
|
package/cjs/controllers/index.js
CHANGED
|
@@ -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) {
|