Skip to content

Commit fca8aba

Browse files
feat: single sign on (#1305)
* feat: single sign on Signed-off-by: bhavanakarwade <[email protected]> * fix: rabbit suggestions Signed-off-by: bhavanakarwade <[email protected]> * update: send verification mail template Signed-off-by: bhavanakarwade <[email protected]> --------- Signed-off-by: bhavanakarwade <[email protected]>
1 parent f5ce197 commit fca8aba

File tree

19 files changed

+826
-433
lines changed

19 files changed

+826
-433
lines changed

.env.demo

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,4 +155,26 @@ OTEL_TRACES_OTLP_ENDPOINT='http://localhost:4318/v1/traces'
155155
OTEL_LOGS_OTLP_ENDPOINT='http://localhost:4318/v1/logs'
156156
OTEL_HEADERS_KEY=88ca6b1XXXXXXXXXXXXXXXXXXXXXXXXXXX
157157
OTEL_LOGGER_NAME='credebl-platform-logger'
158-
HOSTNAME='localhost'
158+
HOSTNAME='localhost'
159+
160+
# SSO
161+
# To add more clients, simply add comma separated values of client names
162+
SUPPORTED_SSO_CLIENTS=CREDEBL
163+
164+
# To add more client add the following variables for each additional client.
165+
# Replace the `CLIENT-NAME` with the appropriate client name as added in `SUPPORTED_SSO_CLIENTS`
166+
# Default client will not need the following details
167+
168+
# CLIENT-NAME_CLIENT_ALIAS=VERIFIER
169+
# # Domain represents the redirection url once the client logs-in
170+
# # TODO: Can be taken from keycloak instead
171+
# CLIENT-NAME_DOMAIN=https://VERIFIER-domain.com
172+
# # Encrypted client credentials using the `CRYPTO_PRIVATE_KEY`
173+
# CLIENT-NAME_KEYCLOAK_MANAGEMENT_CLIENT_ID=
174+
# CLIENT-NAME_KEYCLOAK_MANAGEMENT_CLIENT_SECRET=
175+
176+
# Sample values:
177+
# VERIFIER_CLIENT_ALIAS=VERIFIER
178+
# VERIFIER_DOMAIN=https://VERIFIER-domain.com
179+
# VERIFIER_KEYCLOAK_MANAGEMENT_CLIENT_ID=encryptedKeyCloakClientId
180+
# VERIFIER_KEYCLOAK_MANAGEMENT_CLIENT_SECRET=encryptedKeyCloakClientSecret

.env.sample

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,3 +176,25 @@ OTEL_LOGS_OTLP_ENDPOINT='http://localhost:4318/v1/logs' # Endpoint where lo
176176
OTEL_HEADERS_KEY=88ca6b1XXXXXXXXXXXXXXXXXXXXXXXXXXX # API key or token used for authenticating with the OTel collector (e.g., SigNoz)
177177
OTEL_LOGGER_NAME='credebl-platform-logger' # Name of the logger used for OpenTelemetry log records
178178
HOSTNAME='localhost' # Hostname or unique identifier for the service instance
179+
180+
# SSO
181+
# To add more clients, simply add comma separated values of client names
182+
SUPPORTED_SSO_CLIENTS=CREDEBL
183+
184+
# To add more client add the following variables for each additional client.
185+
# Replace the `CLIENT-NAME` with the appropriate client name as added in `SUPPORTED_SSO_CLIENTS`
186+
# Default client will not need the following details
187+
188+
# CLIENT-NAME_CLIENT_ALIAS=MYAPP
189+
# # Domain represents the redirection url once the client logs-in
190+
# # TODO: Can be taken from keycloak instead
191+
# CLIENT-NAME_DOMAIN=https://myapp.com
192+
# # Encrypted client credentials using the `CRYPTO_PRIVATE_KEY`
193+
# CLIENT-NAME_KEYCLOAK_MANAGEMENT_CLIENT_ID=
194+
# CLIENT-NAME_KEYCLOAK_MANAGEMENT_CLIENT_SECRET
195+
196+
# Sample values:
197+
# VERIFIER_CLIENT_ALIAS=VERIFIER
198+
# VERIFIER_DOMAIN=https://VERIFIER-domain.com
199+
# VERIFIER_KEYCLOAK_MANAGEMENT_CLIENT_ID=encryptedKeyCloakClientId
200+
# VERIFIER_KEYCLOAK_MANAGEMENT_CLIENT_SECRET=encryptedKeyCloakClientSecret

apps/api-gateway/src/authz/authz.controller.ts

Lines changed: 131 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {
1313
} from '@nestjs/common';
1414
import { AuthzService } from './authz.service';
1515
import { CommonService } from '../../../../libs/common/src/common.service';
16-
import { ApiBody, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
16+
import { ApiBody, ApiOperation, ApiQuery, ApiResponse, ApiTags } from '@nestjs/swagger';
1717
import { ApiResponseDto } from '../dtos/apiResponse.dto';
1818
import { UserEmailVerificationDto } from '../user/dto/create-user.dto';
1919
import IResponseType from '@credebl/common/interfaces/response.interface';
@@ -28,27 +28,55 @@ import { ResetPasswordDto } from './dtos/reset-password.dto';
2828
import { ForgotPasswordDto } from './dtos/forgot-password.dto';
2929
import { ResetTokenPasswordDto } from './dtos/reset-token-password';
3030
import { RefreshTokenDto } from './dtos/refresh-token.dto';
31-
31+
import { getDefaultClient } from '../user/utils';
32+
import { ClientAliasValidationPipe } from './decorators/user-auth-client';
3233

3334
@Controller('auth')
3435
@ApiTags('auth')
3536
@UseFilters(CustomExceptionFilter)
3637
export class AuthzController {
3738
private logger = new Logger('AuthzController');
3839

39-
constructor(private readonly authzService: AuthzService,
40-
private readonly commonService: CommonService) { }
40+
constructor(
41+
private readonly authzService: AuthzService,
42+
private readonly commonService: CommonService
43+
) {}
44+
45+
/**
46+
* Fetch client aliase.
47+
*
48+
* @returns Returns client alias and its url.
49+
*/
50+
@Get('/clientAliases')
51+
@ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto })
52+
@ApiOperation({
53+
summary: 'Get client aliases',
54+
description: 'Fetch client aliases and itr url'
55+
})
56+
async getClientAlias(@Res() res: Response): Promise<Response> {
57+
const clientAliases = await this.authzService.getClientAlias();
58+
const finalResponse: IResponseType = {
59+
statusCode: HttpStatus.OK,
60+
message: ResponseMessages.user.success.fetchClientAliases,
61+
data: clientAliases
62+
};
63+
64+
return res.status(HttpStatus.OK).json(finalResponse);
65+
}
4166

4267
/**
4368
* Verify user’s email address.
44-
*
69+
*
4570
* @param email The email address of the user.
4671
* @param verificationcode The verification code sent to the user's email.
47-
* @returns Returns the email verification status.
72+
* @returns Returns the email verification status.
4873
*/
4974
@Get('/verify')
5075
@ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto })
51-
@ApiOperation({ summary: 'Verify user’s email', description: 'Checks if the provided verification code is valid for the given email.' })
76+
@ApiOperation({
77+
summary: 'Verify user’s email',
78+
description: 'Checks if the provided verification code is valid for the given email.'
79+
})
5280
async verifyEmail(@Query() query: EmailVerificationDto, @Res() res: Response): Promise<Response> {
5381
await this.authzService.verifyEmail(query);
5482
const finalResponse: IResponseType = {
@@ -60,15 +88,28 @@ export class AuthzController {
6088
}
6189

6290
/**
63-
* Sends a verification email to the user.
64-
*
65-
* @body UserEmailVerificationDto.
66-
* @returns The status of the verification email.
67-
*/
91+
* Sends a verification email to the user.
92+
*
93+
* @body UserEmailVerificationDto.
94+
* @returns The status of the verification email.
95+
*/
6896
@Post('/verification-mail')
6997
@ApiResponse({ status: HttpStatus.CREATED, description: 'Created', type: ApiResponseDto })
98+
@ApiQuery({
99+
name: 'clientAlias',
100+
required: false,
101+
enum: (process.env.SUPPORTED_SSO_CLIENTS || '')
102+
.split(',')
103+
.map((alias) => alias.trim()?.toUpperCase())
104+
.filter(Boolean)
105+
})
70106
@ApiOperation({ summary: 'Send verification email', description: 'Send verification email to new user' })
71-
async create(@Body() userEmailVerification: UserEmailVerificationDto, @Res() res: Response): Promise<Response> {
107+
async create(
108+
@Query('clientAlias', ClientAliasValidationPipe) clientAlias: string,
109+
@Body() userEmailVerification: UserEmailVerificationDto,
110+
@Res() res: Response
111+
): Promise<Response> {
112+
userEmailVerification.clientAlias = clientAlias ?? (await getDefaultClient()).alias;
72113
await this.authzService.sendVerificationMail(userEmailVerification);
73114
const finalResponse: IResponseType = {
74115
statusCode: HttpStatus.CREATED,
@@ -78,30 +119,32 @@ export class AuthzController {
78119
}
79120

80121
/**
81-
* Registers a new user on the platform.
82-
*
83-
* @body AddUserDetailsDto
84-
* @returns User's registration status and user details
85-
*/
122+
* Registers a new user on the platform.
123+
*
124+
* @body AddUserDetailsDto
125+
* @returns User's registration status and user details
126+
*/
86127
@Post('/signup')
87128
@ApiResponse({ status: HttpStatus.CREATED, description: 'Created', type: ApiResponseDto })
88-
@ApiOperation({ summary: 'Register new user to platform', description: 'Register new user to platform with the provided details.' })
129+
@ApiOperation({
130+
summary: 'Register new user to platform',
131+
description: 'Register new user to platform with the provided details.'
132+
})
89133
async addUserDetails(@Body() userInfo: AddUserDetailsDto, @Res() res: Response): Promise<Response> {
90134
const userData = await this.authzService.addUserDetails(userInfo);
91-
const finalResponse = {
92-
statusCode: HttpStatus.CREATED,
93-
message: ResponseMessages.user.success.create,
94-
data: userData
95-
};
135+
const finalResponse = {
136+
statusCode: HttpStatus.CREATED,
137+
message: ResponseMessages.user.success.create,
138+
data: userData
139+
};
96140
return res.status(HttpStatus.CREATED).json(finalResponse);
97-
98141
}
99142
/**
100-
* Authenticates a user and returns an access token.
101-
*
102-
* @body LoginUserDto
103-
* @returns User's access token details
104-
*/
143+
* Authenticates a user and returns an access token.
144+
*
145+
* @body LoginUserDto
146+
* @returns User's access token details
147+
*/
105148
@Post('/signin')
106149
@ApiOperation({
107150
summary: 'Authenticate the user for the access',
@@ -110,7 +153,6 @@ export class AuthzController {
110153
@ApiResponse({ status: HttpStatus.OK, description: 'Success', type: AuthTokenResponse })
111154
@ApiBody({ type: LoginUserDto })
112155
async login(@Body() loginUserDto: LoginUserDto, @Res() res: Response): Promise<Response> {
113-
114156
if (loginUserDto.email) {
115157
const userData = await this.authzService.login(loginUserDto.email, loginUserDto.password);
116158

@@ -126,60 +168,58 @@ export class AuthzController {
126168
}
127169
}
128170

129-
130171
/**
131-
* Resets user's password.
132-
*
133-
* @body ResetPasswordDto
134-
* @returns The password reset status.
135-
*/
172+
* Resets user's password.
173+
*
174+
* @body ResetPasswordDto
175+
* @returns The password reset status.
176+
*/
136177
@Post('/reset-password')
137178
@ApiOperation({
138179
summary: 'Reset password',
139180
description: 'Allows users to reset a new password which should be different form existing password.'
140-
})
181+
})
141182
@ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto })
142183
async resetPassword(@Body() resetPasswordDto: ResetPasswordDto, @Res() res: Response): Promise<Response> {
143-
144-
const userData = await this.authzService.resetPassword(resetPasswordDto);
145-
const finalResponse: IResponseType = {
146-
statusCode: HttpStatus.OK,
147-
message: ResponseMessages.user.success.resetPassword,
148-
data: userData
149-
};
150-
return res.status(HttpStatus.OK).json(finalResponse);
184+
const userData = await this.authzService.resetPassword(resetPasswordDto);
185+
const finalResponse: IResponseType = {
186+
statusCode: HttpStatus.OK,
187+
message: ResponseMessages.user.success.resetPassword,
188+
data: userData
189+
};
190+
return res.status(HttpStatus.OK).json(finalResponse);
151191
}
152192

153-
/**
154-
* Initiates the password reset process by sending a reset link to the user's email.
155-
*
156-
* @body ForgotPasswordDto
157-
* @returns Status message indicating whether the reset link was sent successfully.
158-
*/
193+
/**
194+
* Initiates the password reset process by sending a reset link to the user's email.
195+
*
196+
* @body ForgotPasswordDto
197+
* @returns Status message indicating whether the reset link was sent successfully.
198+
*/
159199
@Post('/forgot-password')
160200
@ApiOperation({
161201
summary: 'Forgot password',
162202
description: 'Sends a password reset link to the user’s email.'
163203
})
164204
@ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto })
165205
async forgotPassword(@Body() forgotPasswordDto: ForgotPasswordDto, @Res() res: Response): Promise<Response> {
166-
const userData = await this.authzService.forgotPassword(forgotPasswordDto);
167-
const finalResponse: IResponseType = {
168-
statusCode: HttpStatus.OK,
169-
message: ResponseMessages.user.success.resetPasswordLink,
170-
data: userData
171-
};
206+
const userData = await this.authzService.forgotPassword(forgotPasswordDto);
207+
const finalResponse: IResponseType = {
208+
statusCode: HttpStatus.OK,
209+
message: ResponseMessages.user.success.resetPasswordLink,
210+
data: userData
211+
};
172212

173-
return res.status(HttpStatus.OK).json(finalResponse);
213+
return res.status(HttpStatus.OK).json(finalResponse);
174214
}
175215

176-
/**
177-
* Resets the user's password using a verification token.
178-
*
179-
* @param email The email address of the user.
180-
* @body ResetTokenPasswordDto
181-
* @returns Status message indicating whether the password reset was successful.
182-
*/
216+
/**
217+
* Resets the user's password using a verification token.
218+
*
219+
* @param email The email address of the user.
220+
* @body ResetTokenPasswordDto
221+
* @returns Status message indicating whether the password reset was successful.
222+
*/
183223
@Post('/password-reset/:email')
184224
@ApiOperation({
185225
summary: 'Reset password with verification token',
@@ -189,41 +229,38 @@ export class AuthzController {
189229
async resetNewPassword(
190230
@Param('email') email: string,
191231
@Body() resetTokenPasswordDto: ResetTokenPasswordDto,
192-
@Res() res: Response): Promise<Response> {
193-
resetTokenPasswordDto.email = email.trim();
194-
const userData = await this.authzService.resetNewPassword(resetTokenPasswordDto);
195-
const finalResponse: IResponseType = {
196-
statusCode: HttpStatus.OK,
197-
message: ResponseMessages.user.success.resetPassword,
198-
data: userData
199-
};
200-
return res.status(HttpStatus.OK).json(finalResponse);
232+
@Res() res: Response
233+
): Promise<Response> {
234+
resetTokenPasswordDto.email = email.trim();
235+
const userData = await this.authzService.resetNewPassword(resetTokenPasswordDto);
236+
const finalResponse: IResponseType = {
237+
statusCode: HttpStatus.OK,
238+
message: ResponseMessages.user.success.resetPassword,
239+
data: userData
240+
};
241+
return res.status(HttpStatus.OK).json(finalResponse);
201242
}
202243

203-
/**
204-
* Generates a new access token using a refresh token.
205-
*
206-
* @body RefreshTokenDto
207-
* @returns New access token and its details.
208-
*/
244+
/**
245+
* Generates a new access token using a refresh token.
246+
*
247+
* @body RefreshTokenDto
248+
* @returns New access token and its details.
249+
*/
209250
@Post('/refresh-token')
210251
@ApiOperation({
211252
summary: 'Token from refresh token',
212253
description: 'Generates a new access token using a refresh token.'
213254
})
214255
@ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto })
215-
async refreshToken(
216-
@Body() refreshTokenDto: RefreshTokenDto,
217-
@Res() res: Response): Promise<Response> {
218-
const tokenData = await this.authzService.refreshToken(refreshTokenDto.refreshToken);
219-
const finalResponse: IResponseType = {
220-
statusCode: HttpStatus.OK,
221-
message: ResponseMessages.user.success.refreshToken,
222-
data: tokenData
223-
};
256+
async refreshToken(@Body() refreshTokenDto: RefreshTokenDto, @Res() res: Response): Promise<Response> {
257+
const tokenData = await this.authzService.refreshToken(refreshTokenDto.refreshToken);
258+
const finalResponse: IResponseType = {
259+
statusCode: HttpStatus.OK,
260+
message: ResponseMessages.user.success.refreshToken,
261+
data: tokenData
262+
};
224263

225-
return res.status(HttpStatus.OK).json(finalResponse);
226-
264+
return res.status(HttpStatus.OK).json(finalResponse);
227265
}
228-
229-
}
266+
}

0 commit comments

Comments
 (0)