Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .github/ci/docker-compose-test-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
version: '3.8'

services:
mongo:
image: mongo
restart: always
environment:
MONGO_INITDB_ROOT_USERNAME: root
MONGO_INITDB_ROOT_PASSWORD: password
volumes:
- db-data:/data/db

app:
depends_on:
- mongo
build:
context: '${GITHUB_WORKSPACE}'
dockerfile: Dockerfile-ci
environment:
- TEST_FIREBASE_CLIENT_API_KEY=${TEST_FIREBASE_CLIENT_API_KEY}
- GOOGLE_APPLICATION_CREDENTIALS=${GOOGLE_APPLICATION_CREDENTIALS}
ports:
- 8080:8080
command: 'npm run test-ci'

volumes:
db-data:
Binary file added .github/ci/firebase.gpg
Binary file not shown.
7 changes: 7 additions & 0 deletions .github/scripts/decrypt_firebase_secret.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/bin/sh

# Script to decrypt firebase secret

# Create the environment and decrypt the file
mkdir $HOME/secrets
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPTION_PASSPHRASE" --output firebase_secret.json ./.github/ci/firebase.gpg
10 changes: 9 additions & 1 deletion .github/workflows/app-test-container.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,13 @@ jobs:
- name: docker layer caching
uses: satackey/action-docker-layer-caching@v0.0.8
continue-on-error: true
- name: descrypt firebase secret
run: ./.github/scripts/decrypt_firebase_secret.sh
env:
DECRYPTION_PASSPHRASE: ${{ secrets.DECRYPTION_PASSPHRASE }}
- name: Run test in container
run: docker-compose --file docker-compose-test-ci.yml up --build --exit-code-from app
shell: bash
env:
TEST_FIREBASE_CLIENT_API_KEY: ${{ secrets.TEST_FIREBASE_CLIENT_API_KEY }}
GOOGLE_APPLICATION_CREDENTIALS: ./firebase_secret.json
run: docker-compose --file ./.github/ci/docker-compose-test-ci.yml up --build --exit-code-from app
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,6 @@ dist

# TernJS port file
.tern-port

# custom
*firebase-adminsdk*.json
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ COPY . .
# The container environmental variables.
ENV PORT=8080
ENV MONGODB_URI=mongodb://root:password@mongo:27017/softwareRepository?authSource=admin
ENV BCRYPT_SALT_ROUNDS=YOUR_BCRYPT_SALT_ROUNDS
ENV TEST_MONGODB_URI=mongodb://root:password@mongo:27017/softwareRepositoryTest?authSource=admin
ENV JWT_SECRET=YOUR_JWT_SECRET
ENV TEST_FIREBASE_CLIENT_API_KEY=YOUR_TEST_FIREBASE_CLIENT_API_KEY
ENV GOOGLE_APPLICATION_CREDENTIALS=YOUR_GOOGLE_SERVICE_ACCOUNT_FILE_PATH

# The container listens on port 8080.
EXPOSE 8080
Expand Down
34 changes: 34 additions & 0 deletions Dockerfile-ci
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# SoftwareRepository's Dockerfile
# With comments to aid in my learning of docker.

# To use official nodejs base docker image.
FROM node:12

# The working directory where any subsequent instructions in the Dockerfile will be executed on.
WORKDIR /app

# I want to install dependencies first so they can be cache.
# Hence, I copy the package.json file first to the work directory for installing the dependencies in the subsequent command (npm install).
COPY package.json .

# Run npm install to install the dependencies specified in package.json
RUN npm install

# After the dependencies are installed, I copy over the source code of the web application to the current working directory.
# Note: In this case, I add .dockerignore file (similar to .gitignore) and add node_modules to the .dockerignore file so that my local node_modules directory will not be copied to the container's working directory.
COPY . .

# The container environmental variables.
ENV PORT=8080
ENV MONGODB_URI=mongodb://root:password@mongo:27017/softwareRepository?authSource=admin
ENV TEST_MONGODB_URI=mongodb://root:password@mongo:27017/softwareRepositoryTest?authSource=admin
ENV TEST_FIREBASE_CLIENT_API_KEY=YOUR_TEST_FIREBASE_CLIENT_API_KEY
ENV GOOGLE_APPLICATION_CREDENTIALS=YOUR_GOOGLE_SERVICE_ACCOUNT_FILE_PATH

# The container listens on port 8080.
EXPOSE 8080

# Should only have one cmd in a Dockerfile. Tells container how to run the application.
# In this case, the command is npm start.
# An exec form (Array of strings). It does not start up a shell session unlike run.
CMD ["npm", "start"]
8 changes: 4 additions & 4 deletions app.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ const usersRouter = require('./routes/api/users');

const middleware = require('./utils/middleware');
const logger = require('./utils/logger');
const loginRouter = require('./routes/api/auth');
const softwaresRouter = require('./routes/api/softwares');
const authRouter = require('./routes/api/auth');
const softwareRouter = require('./routes/api/software');

const app = express();

Expand Down Expand Up @@ -59,8 +59,8 @@ app.use(
// Routes
app.use(rootRouter);
app.use('/api/users', usersRouter);
app.use('/api/auth', loginRouter);
app.use('/api/softwares', softwaresRouter);
app.use('/api/auth', authRouter);
app.use('/api/software', softwareRouter);

app.use(middleware.unknownEndPoint);
app.use(middleware.errorHandler);
Expand Down
7 changes: 3 additions & 4 deletions controllers/api/authController.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,9 @@ const postAuth = async (req, res) => {
},
);

const passwordCorrect =
user === null
? false
: await bcrypt.compare(body.password, user.passwordHash);
const passwordCorrect = user === null
? false
: await bcrypt.compare(body.password, user.passwordHash);

if (!(user && passwordCorrect)) {
return res.status('401').json({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ const Software = require('../../models/software');
const User = require('../../models/user');
const databaseUtils = require('../../utils/databaseUtils');

const getSoftwares = async (req, res) => {
const getSoftware = async (req, res) => {
const softwares = await Software.find({})
.populate('meta.addedByUser', {
username: 1,
Expand All @@ -16,9 +16,9 @@ const getSoftwares = async (req, res) => {
res.status(200).json(softwares);
};

const postSoftwares = async (req, res) => {
const postSoftware = async (req, res) => {
const { body } = req;
const userId = body.decodedToken.id;
const userId = body.decodedToken.backendId;

// Refer to software Model for required parameters.
const softwareObject = {
Expand Down Expand Up @@ -60,10 +60,10 @@ const postSoftwares = async (req, res) => {
return res.status(201).json(saved);
};

const putSoftware = async (req, res) => {
const patchSoftwareById = async (req, res) => {
const { id } = req.params;
const { body } = req;
const userId = body.decodedToken.id;
const userId = body.decodedToken.backendId;

// To configure dotObject transformation to not modify how the array is represented.
dotObject.keepArray = true;
Expand Down Expand Up @@ -103,7 +103,7 @@ const putSoftware = async (req, res) => {
return res.status(200).json(updated);
};

const getSoftware = async (req, res) => {
const getSoftwareById = async (req, res) => {
const { id } = req.params;

const software = await Software.findById(id);
Expand All @@ -120,7 +120,7 @@ const getSoftware = async (req, res) => {
return res.status(200).json(response);
};

const deleteSoftware = async (req, res) => {
const deleteSoftwareById = async (req, res) => {
const { id } = req.params;

const response = await Software.findByIdAndDelete(id);
Expand All @@ -136,9 +136,9 @@ const deleteSoftware = async (req, res) => {
};

module.exports = {
getSoftwares,
postSoftwares,
putSoftware,
getSoftware,
deleteSoftware,
postSoftware,
patchSoftwareById,
getSoftwareById,
deleteSoftwareById,
};
31 changes: 14 additions & 17 deletions controllers/api/usersController.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const bcrypt = require('bcrypt');
const User = require('../../models/user');
const config = require('../../utils/config');
const firebaseAdmin = require('../../utils/firebaseConfig');
const jwtUtils = require('../../utils/jwtUtils');

const getUsers = async (req, res) => {
const users = await User.find({})
Expand All @@ -16,30 +16,27 @@ const getUsers = async (req, res) => {
const postUsers = async (req, res) => {
const { body } = req;

if (!body.password) {
return res.status(400).json({
error: '`password` is required.',
});
}
const authToken = jwtUtils.getReqAuthToken(req);
const decodedToken = await jwtUtils.verifyAuthToken(authToken, false);

if (body.password.length < 8) {
return res.status(400).json({
error: 'Password has to be at least 8 characters long.',
});
}
const response = await firebaseAdmin.auth().getUser(decodedToken.uid);

const saltRounds = config.BCRYPT_SALT_ROUNDS;
const passwordHash = await bcrypt.hash(body.password, saltRounds);
const userRecord = response.toJSON();

const user = new User({
username: body.username,
name: body.name,
email: body.email,
passwordHash,
name: userRecord.displayName,
email: userRecord.email,
firebaseUid: userRecord.uid,
});

const savedUser = await user.save();

// Set custom claim with backend user id (different from firebase user id)
await firebaseAdmin
.auth()
.setCustomUserClaims(decodedToken.uid, { backendId: savedUser.id });

return res.status(201).json(savedUser);
};

Expand Down
22 changes: 0 additions & 22 deletions docker-compose-test-ci.yml

This file was deleted.

1 change: 1 addition & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
module.exports = {
testEnvironment: 'node',
testPathIgnorePatterns: ['<rootDir>/tests/api/disabledAPINotTested'],
};
12 changes: 4 additions & 8 deletions models/software.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,10 @@ const softwareSchema = new mongoose.Schema(
],
required: [true, 'Software homepage url is required'],
},
platforms: {
type: [
{
type: String,
trim: true,
lowercase: true,
},
],
platform: {
type: String,
trim: true,
lowercase: true,
required: [true, 'Software platform is required'],
},
isActiveDevelopment: {
Expand Down
11 changes: 8 additions & 3 deletions models/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,14 @@ const userSchema = new mongoose.Schema(
validate: [isEmail, 'A valid email address is required'],
set: (value) => normalizeEmail(value),
},
passwordHash: {
password: {
type: String,
required: [true, 'Password hash is required'],
default: '',
},
firebaseUid: {
type: String,
required: [true, 'firebase user id is required.'],
unique: true,
},
roles: {
type: [
Expand Down Expand Up @@ -110,7 +115,7 @@ userSchema.set('toJSON', {
returnedObject.id = returnedObject._id.toString();
delete returnedObject._id;
delete returnedObject.__v;
delete returnedObject.passwordHash;
delete returnedObject.password;
},
});

Expand Down
Loading