Skip to content

Commit

Permalink
fix(authentication-service): submission of encrypted password in auth…
Browse files Browse the repository at this point in the history
…entication service. (#1593)

* fix(authentication-service): submission of encrypted password in authentication service api

submission of encrypted password in authentication service api

GH-1592

* fix(authentication-service): submission of encrypted password in authentication service

submission of encrypted password in authentication service

GH-1592
  • Loading branch information
Surbhi-sharma1 authored Aug 9, 2023
1 parent 8492407 commit 3dcee5c
Show file tree
Hide file tree
Showing 9 changed files with 1,398 additions and 282 deletions.
1 change: 1 addition & 0 deletions services/authentication-service/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ REDIS_PASSWORD=
REDIS_DATABASE=
JWT_PRIVATE_KEY=
JWT_PUBLIC_KEY=
PRIVATE_DECRYPTION_KEY=
JWT_SECRET=
JWT_ISSUER=
USER_TEMP_PASSWORD=
Expand Down
1,561 changes: 1,306 additions & 255 deletions services/authentication-service/package-lock.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions services/authentication-service/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@
"@types/lodash": "^4.14.182",
"@types/moment-timezone": "^0.5.30",
"@types/node": "^18.11.18",
"node-forge": "^1.0.3",
"@types/node-forge": "^1.3.4",
"@types/node-fetch": "^2.6.1",
"@types/passport-apple": "^1.1.1",
"@types/passport-azure-ad": "^4.3.1",
Expand Down
3 changes: 3 additions & 0 deletions services/authentication-service/src/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ import {KeyCloakSignupProvider} from './providers/keycloak-signup.provider';
import {LocalPreSignupProvider} from './providers/local-presignup.provider';
import {LocalSignupProvider} from './providers/local-signup.provider';
import {MfaProvider} from './providers/mfa.provider';
import {PasswordDecryptionProvider} from './providers/password-decryption.provider';
import {repositories} from './repositories';
import {MySequence} from './sequence';
import {LoginHelperService, OtpService} from './services';
Expand Down Expand Up @@ -250,6 +251,8 @@ export class AuthenticationServiceComponent implements Component {
CognitoOauth2SignupProvider;
this.providers[SignUpBindings.LOCAL_SIGNUP_PROVIDER.key] =
LocalSignupProvider;
this.providers[AuthServiceBindings.PASSWORD_DECRYPTION_PROVIDER.key] =
PasswordDecryptionProvider;
this.providers[SignUpBindings.PRE_LOCAL_SIGNUP_PROVIDER.key] =
LocalPreSignupProvider;
this.providers[SignUpBindings.SIGNUP_HANDLER_PROVIDER.key] =
Expand Down
11 changes: 9 additions & 2 deletions services/authentication-service/src/keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@
// https://opensource.org/licenses/MIT
import {BindingKey} from '@loopback/core';
import {BINDING_PREFIX} from '@sourceloop/core';
import {ForgotPasswordHandlerFn, JwtPayloadFn} from './providers';
import {
ForgotPasswordHandlerFn,
JwtPayloadFn,
PasswordDecryptionFn,
} from './providers';
import {
ActorId,
IAuthServiceConfig,
Expand All @@ -29,7 +33,10 @@ export namespace AuthServiceBindings {
export const JWTPayloadProvider = BindingKey.create<JwtPayloadFn>(
`${BINDING_PREFIX}.auth.jwt.payload`,
);

export const PASSWORD_DECRYPTION_PROVIDER =
BindingKey.create<PasswordDecryptionFn>(
`sf.auth.password.decryption.provider`,
);
export const ForgotPasswordHandler =
BindingKey.create<ForgotPasswordHandlerFn>(
`${BINDING_PREFIX}.forgetpassword.handler.provider`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@
import {inject} from '@loopback/context';
import {AnyObject, repository} from '@loopback/repository';
import {
HttpErrors,
RequestContext,
get,
getModelSchemaRef,
HttpErrors,
param,
patch,
post,
requestBody,
RequestContext,
} from '@loopback/rest';
import {
AuthenticateErrorKeys,
Expand All @@ -27,21 +27,23 @@ import {
UserStatus,
X_TS_TYPE,
} from '@sourceloop/core';
import crypto from 'crypto';
import * as jwt from 'jsonwebtoken';
import {
authenticate,
authenticateClient,
AuthenticationBindings,
AuthErrorKeys,
AuthenticationBindings,
ClientAuthCode,
STRATEGY,
authenticate,
authenticateClient,
} from 'loopback4-authentication';
import {authorize, AuthorizeErrorKeys} from 'loopback4-authorization';
import {AuthorizeErrorKeys, authorize} from 'loopback4-authorization';
import moment from 'moment-timezone';
import {ActorId, ExternalTokens, IUserActivity} from '../../types';
import {LoginType} from '../../enums/login-type.enum';
import {AuthServiceBindings} from '../../keys';
import {
LoginActivity,
AuthClient,
LoginActivity,
RefreshToken,
User,
UserTenant,
Expand All @@ -50,13 +52,12 @@ import {
AuthCodeBindings,
AuthCodeGeneratorFn,
CodeReaderFn,
JwtPayloadFn,
JWTSignerFn,
JwtPayloadFn,
} from '../../providers';
import * as jwt from 'jsonwebtoken';
import {
LoginActivityRepository,
AuthClientRepository,
LoginActivityRepository,
OtpCacheRepository,
RefreshTokenRepository,
RevokedTokenRepository,
Expand All @@ -69,6 +70,7 @@ import {
} from '../../repositories';
import {TenantConfigRepository} from '../../repositories/tenant-config.repository';
import {LoginHelperService} from '../../services';
import {ActorId, ExternalTokens, IUserActivity} from '../../types';
import {
AuthRefreshTokenRequest,
AuthTokenRequest,
Expand All @@ -78,8 +80,6 @@ import {
import {AuthUser} from './models/auth-user.model';
import {ResetPassword} from './models/reset-password.dto';
import {TokenResponse} from './models/token-response.dto';
import {LoginType} from '../../enums/login-type.enum';
import crypto from 'crypto';

export class LoginController {
constructor(
Expand Down Expand Up @@ -370,16 +370,36 @@ export class LoginController {
}

let changePasswordResponse: User;

if (req.oldPassword) {
let oldPassword = req.oldPassword;
let newPassword = req.password;
if (process.env.PRIVATE_DECRYPTION_KEY) {
const decryptedOldPassword = await this.userRepo.decryptPassword(
req.oldPassword,
);
const decryptedNewPassword = await this.userRepo.decryptPassword(
req.password,
);
oldPassword = decryptedOldPassword;
newPassword = decryptedNewPassword;
}
changePasswordResponse = await this.userRepo.updatePassword(
req.username,
req.oldPassword,
req.password,
oldPassword,
newPassword,
);
} else {
let newPassword = req.password;
if (process.env.PRIVATE_DECRYPTION_KEY) {
const decryptedPassword = await this.userRepo.decryptPassword(
req.password,
);
newPassword = decryptedPassword;
}
changePasswordResponse = await this.userRepo.changePassword(
req.username,
req.password,
newPassword,
);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import {Provider} from '@loopback/core';
import {HttpErrors} from '@loopback/rest';
import {AuthErrorKeys} from 'loopback4-authentication';
import forge from 'node-forge';
import {PasswordDecryptionFn} from './types';
export class PasswordDecryptionProvider
implements Provider<PasswordDecryptionFn>
{
value(): PasswordDecryptionFn {
return async (password: string) => {
if (!process.env.PRIVATE_DECRYPTION_KEY) {
throw new HttpErrors.Unauthorized(AuthErrorKeys.InvalidCredentials);
}
const privateKey = forge.pki.privateKeyFromPem(
process.env.PRIVATE_DECRYPTION_KEY,
);
const decryptedPassword = privateKey.decrypt(password);
return decryptedPassword;
};
}
}
8 changes: 4 additions & 4 deletions services/authentication-service/src/providers/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,19 @@
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
import {DataObject} from '@loopback/repository';
import * as SamlStrategy from '@node-saml/passport-saml';
import {SignOptions, VerifyOptions} from 'jsonwebtoken';
import {
Cognito,
IAuthClient,
IAuthUser,
Keycloak,
} from 'loopback4-authentication';
import * as AppleStrategy from 'passport-apple';
import * as AzureADStrategy from 'passport-azure-ad';
import * as FacebookStrategy from 'passport-facebook';
import * as GoogleStrategy from 'passport-google-oauth20';
import * as InstagramStrategy from 'passport-instagram';
import * as AzureADStrategy from 'passport-azure-ad';
import * as SamlStrategy from '@node-saml/passport-saml';
import {
AuthClient,
ForgetPasswordResponseDto,
Expand All @@ -23,7 +24,6 @@ import {
UserRelations,
} from '../models';
import {AuthUser, OtpResponse} from '../modules/auth';
import {SignOptions, VerifyOptions} from 'jsonwebtoken';

export type GoogleSignUpFn = (
profile: GoogleStrategy.Profile,
Expand All @@ -40,7 +40,7 @@ export type GooglePostVerifyFn = (
profile: GoogleStrategy.Profile,
user: IAuthUser | null,
) => Promise<IAuthUser | null>;

export type PasswordDecryptionFn = (password: string) => Promise<string>;
export type InstagramSignUpFn = (
profile: InstagramStrategy.Profile,
) => Promise<(User & UserRelations) | null>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,29 +14,30 @@ import {
import {Options} from '@loopback/repository/src/common-types';
import {HttpErrors} from '@loopback/rest';
import {
AuthenticateErrorKeys,
AuthProvider,
AuthenticateErrorKeys,
DefaultUserModifyCrudRepository,
IAuthUserWithPermissions,
ILogger,
LOGGER,
UserStatus,
} from '@sourceloop/core';
import * as bcrypt from 'bcrypt';
import {AuthenticationBindings, AuthErrorKeys} from 'loopback4-authentication';
import {AuthErrorKeys, AuthenticationBindings} from 'loopback4-authentication';
import {AuthServiceBindings} from '../keys';
import {
Tenant,
User,
UserCredentials,
UserRelations,
UserTenant,
} from '../models';
import {PasswordDecryptionFn} from '../providers';
import {AuthDbSourceName} from '../types';
import {OtpRepository} from './otp.repository';
import {TenantRepository} from './tenant.repository';
import {UserCredentialsRepository} from './user-credentials.repository';
import {UserTenantRepository} from './user-tenant.repository';

const saltRounds = 10;
export class UserRepository extends DefaultUserModifyCrudRepository<
User,
Expand Down Expand Up @@ -70,6 +71,8 @@ export class UserRepository extends DefaultUserModifyCrudRepository<
@repository.getter('UserTenantRepository')
protected userTenantRepositoryGetter: Getter<UserTenantRepository>,
@inject(LOGGER.LOGGER_INJECT) private readonly logger: ILogger,
@inject(AuthServiceBindings.PASSWORD_DECRYPTION_PROVIDER)
private readonly passwordDecryptionFn: PasswordDecryptionFn,
) {
super(User, dataSource, getCurrentUser);
this.userTenants = this.createHasManyRepositoryFactoryFor(
Expand Down Expand Up @@ -126,6 +129,11 @@ export class UserRepository extends DefaultUserModifyCrudRepository<
}

async verifyPassword(username: string, password: string): Promise<User> {
let newPassword = password;
if (process.env.PRIVATE_DECRYPTION_KEY) {
const decryptedPassword = await this.passwordDecryptionFn(password);
newPassword = decryptedPassword;
}
const user = await super.findOne({
where: {username: username.toLowerCase()},
});
Expand All @@ -135,15 +143,18 @@ export class UserRepository extends DefaultUserModifyCrudRepository<
} else if (
!creds?.password ||
creds.authProvider !== AuthProvider.INTERNAL ||
!(await bcrypt.compare(password, creds.password))
!(await bcrypt.compare(newPassword, creds.password))
) {
this.logger.error('User creds not found in DB or is invalid');
throw new HttpErrors.Unauthorized(AuthErrorKeys.InvalidCredentials);
} else {
return user;
}
}

async decryptPassword(password: string): Promise<string> {
const decryptedPassword = await this.passwordDecryptionFn(password);
return decryptedPassword;
}
async updatePassword(
username: string,
password: string,
Expand Down

0 comments on commit 3dcee5c

Please sign in to comment.