@solidxai/core 0.1.16-beta.2 → 0.1.16-beta.3
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/CHANGELOG.md +659 -0
- package/dist/dtos/otp-sign-up.dto.d.ts +1 -0
- package/dist/dtos/otp-sign-up.dto.d.ts.map +1 -1
- package/dist/dtos/otp-sign-up.dto.js +12 -1
- package/dist/dtos/otp-sign-up.dto.js.map +1 -1
- package/dist/services/authentication.service.d.ts.map +1 -1
- package/dist/services/authentication.service.js +15 -6
- package/dist/services/authentication.service.js.map +1 -1
- package/package.json +1 -1
- package/postman/signup-issues +3 -2
- package/src/dtos/otp-sign-up.dto.ts +11 -1
- package/src/services/authentication.service.ts +61 -26
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solidxai/core",
|
|
3
|
-
"version": "0.1.16-beta.
|
|
3
|
+
"version": "0.1.16-beta.3",
|
|
4
4
|
"description": "This module is a NestJS module containing all the required core providers required by a Solid application",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
package/postman/signup-issues
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
userType is not set in the case of public registration (pending)
|
|
2
|
+
need to test with transactional and mobile type (pending)
|
|
3
|
+
need to check the oauth flow as well
|
|
@@ -1,17 +1,26 @@
|
|
|
1
|
-
import { IsEmail, IsEnum, IsJSON, IsNotEmpty, IsOptional } from 'class-validator';
|
|
1
|
+
import { IsEmail, IsEnum, IsJSON, IsNotEmpty, IsOptional, IsString } from 'class-validator';
|
|
2
2
|
|
|
3
3
|
import { PasswordlessRegistrationValidateWhatSources } from "../constants";
|
|
4
|
+
import { ApiProperty } from '@nestjs/swagger';
|
|
4
5
|
|
|
5
6
|
export class OTPSignUpDto {
|
|
7
|
+
@IsOptional()
|
|
8
|
+
@IsString()
|
|
9
|
+
@ApiProperty()
|
|
10
|
+
fullName: string;
|
|
11
|
+
|
|
6
12
|
@IsNotEmpty()
|
|
13
|
+
@ApiProperty()
|
|
7
14
|
username: string;
|
|
8
15
|
|
|
9
16
|
@IsOptional()
|
|
10
17
|
@IsEmail()
|
|
18
|
+
@ApiProperty()
|
|
11
19
|
email: string;
|
|
12
20
|
|
|
13
21
|
@IsOptional()
|
|
14
22
|
@IsNotEmpty()
|
|
23
|
+
@ApiProperty()
|
|
15
24
|
mobile: string;
|
|
16
25
|
|
|
17
26
|
@IsOptional()
|
|
@@ -19,5 +28,6 @@ export class OTPSignUpDto {
|
|
|
19
28
|
validationSources: PasswordlessRegistrationValidateWhatSources[] = [];
|
|
20
29
|
|
|
21
30
|
@IsOptional()
|
|
31
|
+
@ApiProperty()
|
|
22
32
|
customPayload: any;
|
|
23
33
|
}
|
|
@@ -70,9 +70,18 @@ interface otp {
|
|
|
70
70
|
|
|
71
71
|
/** Where a signup's role names come from. */
|
|
72
72
|
enum RolesSource {
|
|
73
|
-
/**
|
|
73
|
+
/**
|
|
74
|
+
* Ask nobody; returning [] lets performSignUp apply the configured `defaultRole`.
|
|
75
|
+
*
|
|
76
|
+
* No intent maps here today - it is kept for paths that deliberately bypass the
|
|
77
|
+
* provider. OAuth is the live example: its DTO can never carry a discriminator, so
|
|
78
|
+
* it takes `defaultRole` outright (currently inline in UserService).
|
|
79
|
+
*/
|
|
74
80
|
Default,
|
|
75
|
-
/**
|
|
81
|
+
/**
|
|
82
|
+
* `provider.roles(dto)` - and nothing else. Yields [] when no provider is registered
|
|
83
|
+
* or when the provider declines to name any, which falls through to `defaultRole`.
|
|
84
|
+
*/
|
|
76
85
|
Provider,
|
|
77
86
|
/** The caller's own `dto.roles`. */
|
|
78
87
|
Caller,
|
|
@@ -82,10 +91,18 @@ enum RolesSource {
|
|
|
82
91
|
* The single place that decides, per intent, which entity a signup builds and where
|
|
83
92
|
* its roles come from.
|
|
84
93
|
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
94
|
+
* Where an extension user provider is registered it is the authority on that app's user
|
|
95
|
+
* roles, so every provider-backed intent asks it. `defaultRole` is the *fallback* for
|
|
96
|
+
* when it names none - or when there is no provider at all - not the rule.
|
|
97
|
+
*
|
|
98
|
+
* SelfRegistration and ExtensionModel resolve identically today. They stay separate
|
|
99
|
+
* intents because only SelfRegistration is gated on `allowPublicRegistration`, and
|
|
100
|
+
* because "an anonymous visitor signing themselves up" and "an admin creating a user
|
|
101
|
+
* from the model's own form" are different things that may yet need to diverge.
|
|
102
|
+
*
|
|
103
|
+
* `useProvider` is computable from `rolesSource` today (`Caller` implies false), but the
|
|
104
|
+
* two answer different questions - "which entity?" and "which roles?" - that merely
|
|
105
|
+
* coincide across these intents. Keep both, and change behaviour here, not at a call site.
|
|
89
106
|
*/
|
|
90
107
|
const SIGNUP_POLICY: Record<
|
|
91
108
|
SignupIntent,
|
|
@@ -93,7 +110,7 @@ const SIGNUP_POLICY: Record<
|
|
|
93
110
|
> = {
|
|
94
111
|
[SignupIntent.SelfRegistration]: {
|
|
95
112
|
useProvider: true,
|
|
96
|
-
rolesSource: RolesSource.
|
|
113
|
+
rolesSource: RolesSource.Provider,
|
|
97
114
|
},
|
|
98
115
|
[SignupIntent.ExtensionModel]: {
|
|
99
116
|
useProvider: true,
|
|
@@ -246,12 +263,24 @@ export class AuthenticationService {
|
|
|
246
263
|
dto: Record<string, any>,
|
|
247
264
|
source: RolesSource,
|
|
248
265
|
): string[] {
|
|
266
|
+
// Only `Caller` reads dto.roles. Anywhere else, roles in the body are ignored -
|
|
267
|
+
// which is what keeps an anonymous caller from naming their own on a @Public()
|
|
268
|
+
// endpoint - so say so rather than dropping them silently.
|
|
269
|
+
if (source !== RolesSource.Caller && dto.roles?.length) {
|
|
270
|
+
this.logger.warn(
|
|
271
|
+
`Ignoring caller-supplied roles for "${dto.username}": roles on this path come from ` +
|
|
272
|
+
(source === RolesSource.Provider
|
|
273
|
+
? "the extension user provider"
|
|
274
|
+
: "the configured defaultRole"),
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
|
|
249
278
|
switch (source) {
|
|
250
279
|
case RolesSource.Provider:
|
|
251
|
-
// Sole authority.
|
|
252
|
-
//
|
|
253
|
-
//
|
|
254
|
-
//
|
|
280
|
+
// Sole authority. Reading dto.roles as a preference or fallback would skip
|
|
281
|
+
// roles(), which is where a provider validates its discriminator, and
|
|
282
|
+
// CreateUserDto types roles as UpdateRoleMetadataDto[] where performSignUp
|
|
283
|
+
// expects role-name strings. [] here falls through to `defaultRole`.
|
|
255
284
|
return (
|
|
256
285
|
this.solidRegistry
|
|
257
286
|
.getExtensionUserCreationProvider()
|
|
@@ -262,14 +291,6 @@ export class AuthenticationService {
|
|
|
262
291
|
return dto.roles ?? [];
|
|
263
292
|
|
|
264
293
|
case RolesSource.Default:
|
|
265
|
-
// Public signup: the form has no business naming roles. Returning empty lets
|
|
266
|
-
// performSignUp apply the configured `defaultRole`, rather than adding a
|
|
267
|
-
// second mechanism for the same thing.
|
|
268
|
-
if (dto.roles?.length) {
|
|
269
|
-
this.logger.warn(
|
|
270
|
-
`Ignoring caller-supplied roles on public registration for "${dto.username}"`,
|
|
271
|
-
);
|
|
272
|
-
}
|
|
273
294
|
return [];
|
|
274
295
|
}
|
|
275
296
|
}
|
|
@@ -772,16 +793,31 @@ export class AuthenticationService {
|
|
|
772
793
|
validationSource: string,
|
|
773
794
|
): Promise<User> {
|
|
774
795
|
if (isEmpty(existingUser)) {
|
|
796
|
+
// Resolved before anything is written: where a provider requires its discriminator
|
|
797
|
+
// roles() throws, and that should surface as a rejected request rather than as a
|
|
798
|
+
// half-registered user with no roles.
|
|
799
|
+
const roles = this.resolveSignupRoles(
|
|
800
|
+
signUpDto,
|
|
801
|
+
SIGNUP_POLICY[SignupIntent.SelfRegistration].rolesSource,
|
|
802
|
+
);
|
|
803
|
+
|
|
775
804
|
// A new registration is saved through whichever repository createUser resolved,
|
|
776
805
|
// which is the provider's when the app registers one.
|
|
777
806
|
const { entity: user, repo } = await this.createUser(signUpDto);
|
|
778
807
|
user.active = false; // User will be activated only after OTP verification, hence setting active to false for new user.
|
|
779
808
|
await this.assignRegistrationOtp(validationSource, user);
|
|
780
809
|
await repo.save(user);
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
this.
|
|
784
|
-
|
|
810
|
+
|
|
811
|
+
if (roles.length) {
|
|
812
|
+
await this.userService.addRolesToUser(user.username, roles);
|
|
813
|
+
} else {
|
|
814
|
+
// The provider named none, or there is no provider: fall back to the configured
|
|
815
|
+
// default, matching what performSignUp does on the password paths.
|
|
816
|
+
await this.userService.addRoleToUser(
|
|
817
|
+
user.username,
|
|
818
|
+
this.settingService.getConfigValue<SolidCoreSetting>("defaultRole"),
|
|
819
|
+
);
|
|
820
|
+
}
|
|
785
821
|
return user;
|
|
786
822
|
}
|
|
787
823
|
|
|
@@ -812,9 +848,8 @@ export class AuthenticationService {
|
|
|
812
848
|
entity.username = signUpDto.username;
|
|
813
849
|
entity.email = signUpDto.email;
|
|
814
850
|
entity.mobile = signUpDto.mobile;
|
|
815
|
-
// OTPSignUpDto declares no `fullName`, so username is the only source here. Mirrors
|
|
816
|
-
|
|
817
|
-
entity.fullName = signUpDto.username;
|
|
851
|
+
// OTPSignUpDto declares no `fullName`, so username is the only source here. Mirrors the fallback in populateForSignup, which this path does not go through.
|
|
852
|
+
entity.fullName = signUpDto.fullName?.trim() || signUpDto.username;
|
|
818
853
|
entity.customPayload = signUpDto.customPayload;
|
|
819
854
|
entity.lastLoginProvider = LoginProvider.OTP;
|
|
820
855
|
|