Skip to content

Commit

Permalink
Merge pull request #30 from Selecro/edit-vault-service
Browse files Browse the repository at this point in the history
Edit vault service
  • Loading branch information
Szotkowski authored Sep 19, 2023
2 parents 674d6c0 + 5b7bd96 commit 43d5686
Show file tree
Hide file tree
Showing 14 changed files with 349 additions and 107 deletions.
61 changes: 36 additions & 25 deletions .github/workflows/continuous-deployment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,23 @@ jobs:
- name: Set deployment variables
run: |
if [ "${{ github.ref }}" = "refs/heads/dev" ]; then
echo "EXTPORT=3002" >> $GITHUB_ENV
echo "IMAGE=backend-dev" >> $GITHUB_ENV
echo "VAULT_PORT=8202" >> $GITHUB_ENV
echo "SQLDATABASE=selecro_dev" >> $GITHUB_ENV
echo "DEFAULT_PORT=${{ secrets.DEFAULT_PORT_DEV }}" >> $GITHUB_ENV
echo "SQL_DATABASE=${{ secrets.SQL_DATABASE_DEV }}" >> $GITHUB_ENV
echo "VAULT_PORT=${{ secrets.VAULT_PORT_DEV }}" >> $GITHUB_ENV
echo "UNSEAL_KEY_1=${{ secrets.UNSEAL_KEY_1_DEV }}" >> $GITHUB_ENV
echo "UNSEAL_KEY_2=${{ secrets.UNSEAL_KEY_2_DEV }}" >> $GITHUB_ENV
echo "UNSEAL_KEY_3=${{ secrets.UNSEAL_KEY_3_DEV }}" >> $GITHUB_ENV
echo "ROOT_VAULT_TOKEN=${{ secrets.ROOT_VAULT_TOKEN_DEV }}" >> $GITHUB_ENV
elif [ "${{ github.ref }}" = "refs/heads/main" ]; then
echo "EXTPORT=3001" >> $GITHUB_ENV
echo "IMAGE=backend-main" >> $GITHUB_ENV
echo "VAULT_PORT=8201" >> $GITHUB_ENV
echo "SQLDATABASE=selecro_main" >> $GITHUB_ENV
echo "DEFAULT_PORT=${{ secrets.DEFAULT_PORT_MAIN }}" >> $GITHUB_ENV
echo "SQL_DATABASE=${{ secrets.SQL_DATABASE_MAIN }}" >> $GITHUB_ENV
echo "VAULT_PORT=${{ secrets.VAULT_PORT_MAIN }}" >> $GITHUB_ENV
echo "UNSEAL_KEY_1=${{ secrets.UNSEAL_KEY_1_MAIN }}" >> $GITHUB_ENV
echo "UNSEAL_KEY_2=${{ secrets.UNSEAL_KEY_2_MAIN }}" >> $GITHUB_ENV
echo "UNSEAL_KEY_3=${{ secrets.UNSEAL_KEY_3_MAIN }}" >> $GITHUB_ENV
echo "ROOT_VAULT_TOKEN=${{ secrets.ROOT_VAULT_TOKEN_MAIN }}" >> $GITHUB_ENV
else
echo "Invalid branch for deployment" && exit 1
fi
Expand Down Expand Up @@ -78,27 +86,30 @@ jobs:
docker ps -a | grep ${{ env.IMAGE }} && docker stop ${{ env.IMAGE }} || true && \
docker ps -a | grep ${{ env.IMAGE }} && docker rm ${{ env.IMAGE }} || true && \
docker run \
-e HOST="${{ secrets.HOST }}" \
-e SQLHOST="${{ secrets.SQLHOST }}" \
-e SQLPORT="${{ secrets.SQLPORT }}" \
-e SQLUSER="${{ secrets.SQLUSER }}" \
-e SQLPASSWORD="${{ secrets.SQLPASSWORD }}" \
-e SQLDATABASE="${{ env.SQLDATABASE }}" \
-e TOKEN="${{ secrets.TOKEN }}" \
-e EXTPORT="${{ env.EXTPORT }}" \
-e EMAILHOST="${{ secrets.EMAILHOST }}" \
-e EMAILPORT="${{ secrets.EMAILPORT }}" \
-e EMAILUSER="${{ secrets.EMAILUSER }}" \
-e EMAILPASSWORD="${{ secrets.EMAILPASSWORD }}" \
-e JWT_SECRET="$${{ secrets.JWT_SECRET }}" \
-e UNSEAL_KEY_1="${{ secrets.UNSEAL_KEY_1 }}" \
-e UNSEAL_KEY_2="${{ secrets.UNSEAL_KEY_2 }}" \
-e DEFAULT_HOST="${{ secrets.DEFAULT_HOST }}" \
-e DEFAULT_PORT="${{ env.DEFAULT_PORT }}" \
-e JWT_SECRET="${{ secrets.JWT_SECRET }}" \
-e JWT_SECRET_EMAIL="${{ secrets.JWT_SECRET_EMAIL }}" \
-e JWT_SECRET_SIGNUP="${{ secrets.JWT_SECRET_SIGNUP }}" \
-e SQL_HOST="${{ secrets.SQL_HOST }}" \
-e SQL_PORT="${{ secrets.SQL_PORT }}" \
-e SQL_USER="${{ secrets.SQL_USER }}" \
-e SQL_PASSWORD="${{ secrets.SQL_PASSWORD }}" \
-e SQL_DATABASE="${{ env.SQL_DATABASE }}" \
-e EMAIL_HOST="${{ secrets.EMAIL_HOST }}" \
-e EMAIL_PORT="${{ secrets.EMAIL_PORT }}" \
-e EMAIL_USER="${{ secrets.EMAIL_USER }}" \
-e EMAIL_PASSWORD="${{ secrets.EMAIL_PASSWORD }}" \
-e VAULT_URL="${{ secrets.VAULT_URL }}" \
-e VAULT_URL="${{ secrets.VAULT_URL }}" \
-e VAULT_PORT="${{ env.VAULT_PORT }}" \
-e CLIENT_ID="${{ secrets.CLIENT_ID }}" \
-e ROOT_VAULT="${{ secrets.ROOT_VAULT }}" \
-e INSTRUCTION_KEY="${{ secrets.INSTRUCTION_KEY }}" \
-e INSTRUCTION_KEY_PERMISSIONS="${{ secrets.INSTRUCTION_KEY_PERMISSIONS }}" \
-e UNSEAL_KEY_1="${{ env.UNSEAL_KEY_1 }}" \
-e UNSEAL_KEY_2="${{ env.UNSEAL_KEY_2 }}" \
-e UNSEAL_KEY_3="${{ env.UNSEAL_KEY_3 }}" \
-e ROOT_VAULT_TOKEN="${{ env.ROOT_VAULT_TOKEN }}" \
-e IMGUR_CLIENT_ID="${{ secrets.IMGUR_CLIENT_ID }}" \
-e INSTRUCTION_KEY_PREMIUM="${{ secrets.INSTRUCTION_KEY_PREMIUM }}" \
-e INSTRUCTION_KEY_PREMIUM_PERMISSIONS="${{ secrets.INSTRUCTION_KEY_PREMIUM_PERMISSIONS }}" \
--name ${{ env.IMAGE }} -dp ${{ env.EXTPORT }}:${{ env.EXTPORT }} \
selecro/${{ env.IMAGE }}:${{ github.ref_name }}-${{ env.SHORT_SHA }} && \
docker update --restart unless-stopped ${{ env.IMAGE }} && exit
Expand Down
2 changes: 1 addition & 1 deletion src/application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ export class SelecroBackendApplication extends BootMixin(

this.bind('services.jwt.service').toClass(JWTService);
this.bind('authentication.jwt.expiresIn').to('32d');
this.bind('authentication.jwt.secret').to(process.env.TOKEN);
this.bind('authentication.jwt.secret').to(process.env.JWT_SECRET);
this.bind('services.hasher').toClass(BcryptHasher);
this.bind('services.hasher.rounds').to(10);
this.bind('services.user.service').toClass(MyUserService);
Expand Down
7 changes: 4 additions & 3 deletions src/controllers/user-instruction.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ export class UserInstructionController {
if (!key) {
throw new HttpErrors.Unauthorized('Key not providen');
}
const instructionKey = process.env.INSTRUCTION_KEY ?? '';
const instructionKey = process.env.INSTRUCTION_KEY_PREMIUM ?? '';
const keyMatch = await this.hasher.comparePassword(key, instructionKey);
if (!keyMatch) {
throw new HttpErrors.Unauthorized('Invalid password');
Expand Down Expand Up @@ -440,7 +440,7 @@ export class UserInstructionController {
if (!instruction) {
throw new HttpErrors.NotFound('Instruction not found');
}
const instructionKey = process.env.INSTRUCTION_KEY ?? '';
const instructionKey = process.env.INSTRUCTION_KEY_PREMIUM ?? '';
const keyMatch = await this.hasher.comparePassword(
request.key,
instructionKey,
Expand Down Expand Up @@ -664,7 +664,8 @@ export class UserInstructionController {
@param.query.number('instructionId') instructionId: number,
@param.query.number('userId') userId: number,
): Promise<boolean> {
const instructionKey = process.env.INSTRUCTION_KEY_PERMISSIONS ?? '';
const instructionKey =
process.env.INSTRUCTION_KEY_PREMIUM_PERMISSIONS ?? '';
const keyMatch = await this.hasher.comparePassword(
request.key,
instructionKey,
Expand Down
176 changes: 154 additions & 22 deletions src/controllers/user.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {authenticate} from '@loopback/authentication';
import {
Credentials,
JWTService,
TokenObject,
UserCredentials,
} from '@loopback/authentication-jwt';
import {inject} from '@loopback/context';
Expand Down Expand Up @@ -62,7 +63,7 @@ export class UserController {
@repository(StepRepository) public stepRepository: StepRepository,
@repository(UserLinkRepository)
public userLinkRepository: UserLinkRepository,
) {}
) { }

@post('/login', {
responses: {
Expand Down Expand Up @@ -99,7 +100,7 @@ export class UserController {
},
})
credentials: Credentials,
): Promise<{token: string; tokenKMS: string}> {
): Promise<string> {
const user = await this.userService.verifyCredentials(credentials);
const userProfile = this.userService.convertToUserProfile(user);
const existingUser = await this.userRepository.findOne({
Expand All @@ -109,11 +110,65 @@ export class UserController {
throw new HttpErrors.UnprocessableEntity('email is not verified');
}
const token = await this.jwtService.generateToken(userProfile);
const tokenKMS = await this.vaultService.authenticate(
String(existingUser.id),
credentials.password,
);
return {token, tokenKMS};
return token;
}

@post('/refresh-token', {
responses: {
'200': {
description: 'Refresh token',
content: {
'application/json': {
schema: {
type: 'object',
properties: {
token: {type: 'string'},
},
},
},
},
},
},
})
async refreshToken(
@requestBody({
content: {
'application/json': {
schema: {
type: 'object',
properties: {
token: {type: 'string'},
},
required: [
'token',
],
},
},
},
})
requestBody: {refreshToken: string},
): Promise<TokenObject> {
try {
const {refreshToken} = requestBody;
if (!refreshToken) {
throw new HttpErrors.Unauthorized(
`Error verifying token: 'refresh token' is null`,
);
}
const userRefreshData = await this.jwtService.verifyToken(refreshToken);
const user = await this.userRepository.findById(
userRefreshData.userId.toString(),
);
const userProfile: UserProfile = this.userService.convertToUserProfile(user);
const token = await this.jwtService.generateToken(userProfile);
return {
accessToken: token,
};
} catch (error) {
throw new HttpErrors.Unauthorized(
`Error verifying token: ${error.message}`,
);
}
}

@post('/signup', {
Expand All @@ -123,7 +178,10 @@ export class UserController {
content: {
'application/json': {
schema: {
type: 'boolean',
type: 'object',
properties: {
token: {type: 'string'},
},
},
},
},
Expand All @@ -142,7 +200,6 @@ export class UserController {
password0: {type: 'string'},
password1: {type: 'string'},
language: {enum: Object.values(Language)},
wrappedDEK: {type: 'string'},
kekSalt: {type: 'string'},
initializationVector: {type: 'string'},
},
Expand All @@ -152,7 +209,6 @@ export class UserController {
'password0',
'password1',
'language',
'wrappedDEK',
'kekSalt',
'initializationVector',
],
Expand All @@ -178,26 +234,100 @@ export class UserController {
const hashedPassword = await this.hasher.hashPassword(
credentials.password0,
);
await this.vaultService.createUser(
String(credentials.id),
credentials.password0,
);
const tokenKMS = await this.vaultService.authenticate(
String(credentials.id),
credentials.password0,
);
const newUser = new User({
email: credentials.email,
username: credentials.username,
passwordHash: hashedPassword,
wrappedDEK: credentials.wrappedDEK.toString('base64'),
wrappedDEK: 'null',
kekSalt: credentials.kekSalt,
initializationVector: credentials.initializationVector.toString('base64'),
language: credentials.language,
});
const dbUser = await this.userRepository.create(newUser);
await this.vaultService.createUserPolicy(String(dbUser.id));
await this.vaultService.createUser(
String(dbUser.id),
credentials.password0,
);
await this.vaultService.createUserKey(String(dbUser.id));
await this.emailService.sendRegistrationEmail(dbUser);
return tokenKMS;
const secret = process.env.JWT_SECRET_SIGNUP ?? '';
const userId = dbUser.id;
const token = jwt.sign({userId}, secret, {
expiresIn: 60,
algorithm: 'HS256',
});
return token;
}

@post('/save-Wrapped-DEK', {
responses: {
'200': {
description: 'Save Wrapped DEK',
content: {
'application/json': {
schema: {
type: 'boolean',
},
},
},
},
},
})
async saveWrappedDEK(
@requestBody({
content: {
'application/json': {
schema: {
type: 'object',
properties: {
token: {type: 'string'},
wrappedDEK: {type: 'string'},
},
required: ['token', 'wrappedDEK'],
},
},
},
})
request: {
token: string;
wrappedDEK: string;
},
): Promise<boolean> {
interface DecodedToken {
userId: number;
iat: number;
exp: number;
}
try {
const {token} = request;
const secret = process.env.JWT_SECRET_SIGNUP ?? '';
const decodedToken = jwt.verify(token, secret) as DecodedToken;
const {userId} = decodedToken;
const user = await this.userRepository.findById(userId);
if (!user) {
throw new HttpErrors.UnprocessableEntity('User not found');
}
if (user.wrappedDEK !== 'null') {
throw new HttpErrors.UnprocessableEntity('DEK already saved');
}
await this.userRepository.updateById(user.id, {
wrappedDEK: request.wrappedDEK,
});
return true;
} catch (error) {
if (error.name === 'TokenExpiredError') {
throw new HttpErrors.UnprocessableEntity(
'Verification token has expired',
);
} else if (error.name === 'JsonWebTokenError') {
throw new HttpErrors.UnprocessableEntity('Invalid verification token');
} else {
throw new HttpErrors.UnprocessableEntity(
'Failed to update user email verification status',
);
}
}
}

@post('/verify-email', {
Expand Down Expand Up @@ -239,7 +369,7 @@ export class UserController {
}
try {
const {token} = request;
const secret = process.env.JWT_SECRET ?? '';
const secret = process.env.JWT_SECRET_EMAIL ?? '';
const decodedToken = jwt.verify(token, secret) as DecodedToken;
const {userId} = decodedToken;
const user = await this.userRepository.findById(userId);
Expand Down Expand Up @@ -349,7 +479,7 @@ export class UserController {
exp: number;
}
try {
const secret = process.env.JWT_SECRET ?? '';
const secret = process.env.JWT_SECRET_EMAIL ?? '';
const decodedToken = jwt.verify(request.token, secret) as DecodedToken;
const {userData} = decodedToken;
const user = await this.userRepository.findById(userData);
Expand Down Expand Up @@ -537,7 +667,9 @@ export class UserController {
if (!passwordMatched) {
throw new HttpErrors.Unauthorized('Password is not valid');
}
await this.vaultService.deleteUserKey(String(userOriginal.id));
await this.vaultService.deleteUser(String(userOriginal.id));
await this.vaultService.deleteUserPolicy(String(userOriginal.id));
if (userOriginal.deleteHash) {
await this.imgurService.deleteImage(userOriginal.deleteHash);
}
Expand Down
10 changes: 5 additions & 5 deletions src/datasources/db.datasource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ dotenv.config();
const config = {
name: 'db',
connector: 'postgresql',
host: process.env.SQLHOST,
port: Number(process.env.SQLPORT),
user: process.env.SQLUSER,
password: process.env.SQLPASSWORD,
database: process.env.SQLDATABASE,
host: process.env.SQL_HOST,
port: Number(process.env.SQL_PORT),
user: process.env.SQL_USER,
password: process.env.SQL_PASSWORD,
database: process.env.SQL_DATABASE,
};

// Observe application's life cycle to disconnect the datasource when
Expand Down
Loading

0 comments on commit 43d5686

Please sign in to comment.