Skip to content

Commit 6d83aa4

Browse files
prernagpprernagp
authored andcommitted
feat(authentication-service): added ttl to public key repo
BREAKING CHANGE: issue-2034
1 parent 29f9063 commit 6d83aa4

File tree

12 files changed

+173
-271
lines changed

12 files changed

+173
-271
lines changed

services/authentication-service/README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -501,6 +501,29 @@ Authenttication service can be used as a identity server. Following endpoints ha
501501
</tbody>
502502
</table>
503503

504+
Here is the flow diagram to understand how IDP Server works:-
505+
506+
```mermaid
507+
sequenceDiagram
508+
participant Client
509+
participant IdP as ARC IDP
510+
participant Authentication Service
511+
512+
Client->>IdP: GET /.well-known/openid-configuration
513+
IdP-->>Client: Returns IdP config (includes jwks_uri)
514+
515+
Client->>IdP: GET JWKS (using jwks_uri /connect/get-keys)
516+
IdP-->>Client: Returns JWKS (public keys)
517+
518+
Client->>Client: Decode JWT token\nExtract kid from header
519+
520+
Client->>Client: Find public key from JWKS by kid
521+
522+
Client->>Authentication Service: Send JWT token + public key
523+
524+
Authentication Service->>Authentication Service: Verify JWT signature using public key
525+
```
526+
504527
### Setting up a `DataSource`
505528

506529
Here is a sample Implementation `DataSource` implementation using environment variables and PostgreSQL as the data source. The `auth-multitenant-example` utilizes both Redis and PostgreSQL as data sources.

services/authentication-service/src/__tests__/unit/sequelize/sequelize.component.unit.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import {
1313
import {AppleLoginController} from '../../../modules/auth/controllers';
1414
import {
1515
AuthClientRepository,
16-
LoginActivityRepository,
1716
RoleRepository,
1817
TenantConfigRepository,
1918
UserCredentialsRepository,
@@ -144,7 +143,6 @@ describe('Sequelize Component', () => {
144143
UserLevelResourceRepository,
145144
UserLevelPermissionRepository,
146145
UserTenantRepository,
147-
LoginActivityRepository,
148146
],
149147
props: [
150148
'authClientRepository',
@@ -155,7 +153,6 @@ describe('Sequelize Component', () => {
155153
'userResourcesRepository',
156154
'utPermsRepo',
157155
'userTenantRepo',
158-
'loginActivityRepo',
159156
],
160157
},
161158
];

services/authentication-service/src/controllers/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {ForgetPasswordController} from './forget-password.controller';
2121
import {LoginActivityController} from './login-activity.controller';
2222
import {SignupRequestController} from './signup-request.controller';
2323

24+
export * from '../modules/auth/controllers/identity-server.controller';
2425
export * from '../modules/auth/controllers/login.controller';
2526
export * from '../modules/auth/controllers/logout.controller';
2627
export * from '../modules/auth/controllers/otp.controller';

services/authentication-service/src/modules/auth/controllers/identity-server.controller.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ export class IdentityServerController {
174174
},
175175
})
176176
async rotateKeys(): Promise<void> {
177-
return this.idpLoginService.generateNewKey();
177+
return this.idpLoginService.generateNewKey(true);
178178
}
179179

180180
@authorize({permissions: ['*']})

services/authentication-service/src/modules/auth/controllers/login.controller.ts

Lines changed: 9 additions & 192 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import {inject} from '@loopback/context';
66
import {AnyObject, DataObject, Model, repository} from '@loopback/repository';
77
import {
88
HttpErrors,
9-
RequestContext,
109
get,
1110
getModelSchemaRef,
1211
oas,
@@ -28,7 +27,6 @@ import {
2827
UserStatus,
2928
X_TS_TYPE,
3029
} from '@sourceloop/core';
31-
import crypto from 'crypto';
3230
import {
3331
AuthErrorKeys,
3432
AuthenticationBindings,
@@ -38,27 +36,15 @@ import {
3836
authenticateClient,
3937
} from 'loopback4-authentication';
4038
import {AuthorizeErrorKeys, authorize} from 'loopback4-authorization';
41-
import moment from 'moment-timezone';
4239
import {LoginType} from '../../../enums';
43-
import {AuthServiceBindings} from '../../../keys';
44-
import {
45-
AuthClient,
46-
LoginActivity,
47-
RefreshToken,
48-
User,
49-
UserTenant,
50-
} from '../../../models';
40+
import {AuthClient, RefreshToken, User} from '../../../models';
5141
import {
5242
AuthCodeBindings,
5343
AuthCodeGeneratorFn,
54-
JWTSignerFn,
55-
JWTVerifierFn,
56-
JwtPayloadFn,
5744
UserValidationServiceBindings,
5845
} from '../../../providers';
5946
import {
6047
AuthClientRepository,
61-
LoginActivityRepository,
6248
OtpCacheRepository,
6349
RefreshTokenRepository,
6450
RevokedTokenRepository,
@@ -71,12 +57,7 @@ import {
7157
UserTenantRepository,
7258
} from '../../../repositories';
7359
import {IdpLoginService, LoginHelperService} from '../../../services';
74-
import {
75-
ActorId,
76-
ExternalTokens,
77-
IUserActivity,
78-
UserValidationFn,
79-
} from '../../../types';
60+
import {UserValidationFn} from '../../../types';
8061
import {
8162
AuthRefreshTokenRequest,
8263
AuthTokenRequest,
@@ -116,27 +97,14 @@ export class LoginController {
11697
@repository(UserCredentialsRepository)
11798
public userCredsRepository: UserCredentialsRepository,
11899
@inject(LOGGER.LOGGER_INJECT) public logger: ILogger,
119-
@inject(AuthServiceBindings.JWTPayloadProvider)
120-
private readonly getJwtPayload: JwtPayloadFn,
121100
@inject('services.LoginHelperService')
122101
private readonly loginHelperService: LoginHelperService,
123102
@inject('services.IdpLoginService')
124103
private readonly idpLoginService: IdpLoginService,
125104
@inject(AuthCodeBindings.AUTH_CODE_GENERATOR_PROVIDER)
126105
private readonly getAuthCode: AuthCodeGeneratorFn,
127-
@inject(AuthCodeBindings.JWT_SIGNER)
128-
private readonly jwtSigner: JWTSignerFn<object>,
129-
@repository(LoginActivityRepository)
130-
private readonly loginActivityRepo: LoginActivityRepository,
131-
@inject(AuthServiceBindings.ActorIdKey)
132-
private readonly actorKey: ActorId,
133-
@inject.context() private readonly ctx: RequestContext,
134106
@inject(UserValidationServiceBindings.VALIDATE_USER)
135107
private readonly userValidationProvider: UserValidationFn,
136-
@inject(AuthCodeBindings.JWT_VERIFIER.key)
137-
private readonly jwtVerifier: JWTVerifierFn<AnyObject>,
138-
@inject(AuthServiceBindings.MarkUserActivity, {optional: true})
139-
private readonly userActivity?: IUserActivity,
140108
) {}
141109

142110
@authenticateClient(STRATEGY.CLIENT_PASSWORD)
@@ -232,7 +200,11 @@ export class LoginController {
232200
await this.userRepo.updateLastLogin(payload.user.id);
233201
}
234202

235-
return await this.createJWT(payload, this.client, LoginType.ACCESS);
203+
return await this.idpLoginService.createJWT(
204+
payload,
205+
this.client,
206+
LoginType.ACCESS,
207+
);
236208
} catch (error) {
237209
this.logger.error(error);
238210
throw new HttpErrors.Unauthorized(AuthErrorKeys.InvalidCredentials);
@@ -297,7 +269,7 @@ export class LoginController {
297269
);
298270

299271
if (isAuthenticated) {
300-
return this.createJWT(
272+
return this.idpLoginService.createJWT(
301273
{
302274
clientId: payload.refreshPayload.clientId,
303275
userId: payload.refreshPayload.userId,
@@ -546,7 +518,7 @@ export class LoginController {
546518
}
547519

548520
const payload = await this.createTokenPayload(req);
549-
return this.createJWT(
521+
return this.idpLoginService.createJWT(
550522
{
551523
clientId: payload.refreshPayload.clientId,
552524
user: this.user,
@@ -590,159 +562,4 @@ export class LoginController {
590562
authClient: authClient,
591563
};
592564
}
593-
private async createJWT(
594-
payload: ClientAuthCode<User, typeof User.prototype.id> & ExternalTokens,
595-
authClient: AuthClient,
596-
loginType: LoginType,
597-
tenantId?: string,
598-
): Promise<TokenResponse> {
599-
try {
600-
const size = 32;
601-
const ms = 1000;
602-
let user: User | undefined;
603-
if (payload.user) {
604-
user = payload.user;
605-
} else if (payload.userId) {
606-
user = await this.userRepo.findById(payload.userId, {
607-
include: [
608-
{
609-
relation: 'defaultTenant',
610-
},
611-
],
612-
});
613-
if (payload.externalAuthToken && payload.externalRefreshToken) {
614-
(user as AuthUser).externalAuthToken = payload.externalAuthToken;
615-
(user as AuthUser).externalRefreshToken =
616-
payload.externalRefreshToken;
617-
}
618-
} else {
619-
// Do nothing and move ahead
620-
}
621-
if (!user) {
622-
throw new HttpErrors.Unauthorized(
623-
AuthenticateErrorKeys.UserDoesNotExist,
624-
);
625-
}
626-
const data: AnyObject = await this.getJwtPayload(
627-
user,
628-
authClient,
629-
tenantId,
630-
);
631-
const accessToken = await this.jwtSigner(data, {
632-
expiresIn: authClient.accessTokenExpiration,
633-
});
634-
const refreshToken: string = crypto.randomBytes(size).toString('hex');
635-
// Set refresh token into redis for later verification
636-
await this.refreshTokenRepo.set(
637-
refreshToken,
638-
{
639-
clientId: authClient.clientId,
640-
userId: user.id,
641-
username: user.username,
642-
accessToken,
643-
externalAuthToken: (user as AuthUser).externalAuthToken,
644-
externalRefreshToken: (user as AuthUser).externalRefreshToken,
645-
tenantId: data.tenantId,
646-
},
647-
{ttl: authClient.refreshTokenExpiration * ms},
648-
);
649-
650-
const userTenant = await this.userTenantRepo.findOne({
651-
where: {userId: user.id},
652-
});
653-
if (this.userActivity?.markUserActivity)
654-
this.markUserActivity(user, userTenant, {...data}, loginType);
655-
656-
return new TokenResponse({
657-
accessToken,
658-
refreshToken,
659-
expires: moment()
660-
.add(authClient.accessTokenExpiration, 's')
661-
.toDate()
662-
.getTime(),
663-
});
664-
} catch (error) {
665-
this.logger.error(error);
666-
if (HttpErrors.HttpError.prototype.isPrototypeOf(error)) {
667-
throw error;
668-
} else {
669-
throw new HttpErrors.Unauthorized(AuthErrorKeys.InvalidCredentials);
670-
}
671-
}
672-
}
673-
674-
private markUserActivity(
675-
user: User,
676-
userTenant: UserTenant | null,
677-
payload: AnyObject,
678-
loginType: LoginType,
679-
) {
680-
const size = 16;
681-
const encryptionKey = process.env.ENCRYPTION_KEY;
682-
683-
if (encryptionKey) {
684-
const iv = crypto.randomBytes(size);
685-
686-
/* encryption of IP Address */
687-
const cipherIp = crypto.createCipheriv('aes-256-gcm', encryptionKey, iv);
688-
const ip =
689-
this.ctx.request.headers['x-forwarded-for']?.toString() ??
690-
this.ctx.request.socket.remoteAddress?.toString() ??
691-
'';
692-
const encyptIp = Buffer.concat([
693-
cipherIp.update(ip, 'utf8'),
694-
cipherIp.final(),
695-
]);
696-
const authTagIp = cipherIp.getAuthTag();
697-
const ipAddress = JSON.stringify({
698-
iv: iv.toString('hex'),
699-
encryptedData: encyptIp.toString('hex'),
700-
authTag: authTagIp.toString('hex'),
701-
});
702-
703-
/* encryption of Paylolad Address */
704-
const cipherPayload = crypto.createCipheriv(
705-
'aes-256-gcm',
706-
encryptionKey,
707-
iv,
708-
);
709-
const activityPayload = JSON.stringify(payload);
710-
const encyptPayload = Buffer.concat([
711-
cipherPayload.update(activityPayload, 'utf8'),
712-
cipherPayload.final(),
713-
]);
714-
const authTagPayload = cipherIp.getAuthTag();
715-
const tokenPayload = JSON.stringify({
716-
iv: iv.toString('hex'),
717-
encryptedData: encyptPayload.toString('hex'),
718-
authTag: authTagPayload.toString('hex'),
719-
});
720-
// make an entry to mark the users login activity
721-
let actor: string;
722-
let tenantId: string;
723-
if (userTenant) {
724-
actor = userTenant[this.actorKey]?.toString() ?? '0';
725-
tenantId = userTenant.tenantId;
726-
} else {
727-
actor = user['id']?.toString() ?? '0';
728-
tenantId = user.defaultTenantId;
729-
}
730-
const loginActivity = new LoginActivity({
731-
actor,
732-
tenantId,
733-
loginTime: new Date(),
734-
tokenPayload,
735-
loginType,
736-
deviceInfo: this.ctx.request.headers['user-agent']?.toString(),
737-
ipAddress,
738-
});
739-
this.loginActivityRepo.create(loginActivity).catch(() => {
740-
this.logger.error(
741-
`Failed to add the login activity => ${JSON.stringify(
742-
loginActivity,
743-
)}`,
744-
);
745-
});
746-
}
747-
}
748565
}

0 commit comments

Comments
 (0)