Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Finished user managment #92

Merged
merged 23 commits into from
Apr 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
1973385
Added cookie managment in the whole application
Mister-Mario Apr 14, 2024
a65f6e5
Forgot to add the token verifier on some endpoints
Mister-Mario Apr 14, 2024
e9ad477
Added token to the test requests of the gateway service
Mister-Mario Apr 14, 2024
cbb7d35
Mocked jwt in gateway tests
Mister-Mario Apr 14, 2024
32b7a62
Added test to check no token requests
Mister-Mario Apr 14, 2024
dd1c1fe
Removed unused import
Mister-Mario Apr 14, 2024
24931ad
Added logout functionality
Mister-Mario Apr 14, 2024
24f10df
Added restrictions to routes in the webapp
Mister-Mario Apr 14, 2024
490c2ba
Added a reload when logging out too
Mister-Mario Apr 14, 2024
62d7d76
Added new test case for code cover
Mister-Mario Apr 14, 2024
46ca537
Added email to users at the backend
Mister-Mario Apr 14, 2024
257db61
Removed weird import prob vs code stuff
Mister-Mario Apr 14, 2024
57b44f3
Added changes to fix code duplication issues and security flaws
Mister-Mario Apr 14, 2024
22dba52
Try to reduce duplicate code
Mister-Mario Apr 14, 2024
bb17d39
Added email field on login and register and be able to login with use…
Mister-Mario Apr 15, 2024
72850e7
Merge branch 'master' into 88-users-enhancenment
Mister-Mario Apr 21, 2024
51ff839
Fixed all tests that did not use the Cookie and the email
Mister-Mario Apr 21, 2024
de0e44d
Now registering is the same as login, added e2e tests
Mister-Mario Apr 22, 2024
7d79eb4
Made npm install to sync package.json and package-lock.json
Mister-Mario Apr 22, 2024
975cca0
Forgot to remove headless mode
Mister-Mario Apr 22, 2024
19a68d3
Now e2e should work
Mister-Mario Apr 22, 2024
e27f842
Added validations to solve sonarcloud security ranking
Mister-Mario Apr 22, 2024
1b05925
Fixed addUser test failing cause the new endpoint
Mister-Mario Apr 22, 2024
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
2 changes: 2 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ jobs:
- uses: actions/checkout@v4
- name: Publish to Registry
uses: elgohr/Publish-Docker-Github-Action@v5
env:
JWT_KEY: ${{secrets.JWT_KEY}}
with:
name: arquisoft/wiq_en1b/authservice
username: ${{ github.actor }}
Expand Down
163 changes: 117 additions & 46 deletions gatewayservice/gateway-service.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const promBundle = require('express-prom-bundle');
const swaggerUi = require('swagger-ui-express');
const fs = require("fs")
const YAML = require('yaml')

const jwt = require('jsonwebtoken');
const app = express();
const port = 8000;

Expand All @@ -33,7 +33,7 @@ app.post('/login', async (req, res) => {
const authResponse = await axios.post(authServiceUrl+'/login', req.body);
res.json(authResponse.data);
} catch (error) {
res.status(error.response.status).json({ error: error.response.data.error });
manageError(error)
}
});

Expand All @@ -43,103 +43,130 @@ app.post('/adduser', async (req, res) => {
const userResponse = await axios.post(userServiceUrl+'/adduser', req.body);
res.json(userResponse.data);
} catch (error) {
res.status(error.response.status).json({ error: error.response.data.error });
manageError(error);

}
});

app.get('/questions', async (req, res) => {
app.get('/questions', verifyToken, async (req, res) => {
try {

// Forward the question request to the quetion service
const questionResponse = await axios.get(questionServiceUrl+'/questions');
res.json(questionResponse.data);
} catch (error) {
res.status(error.response.status).json({ error: error.response.data.error });
manageError(error)
}
});

app.get('/questions/:lang/:amount/:type', async (req, res) => {
try {
const lang = req.params.lang.toString();
const amount = req.params.amount.toString();
const type = req.params.type.toString();
// Forward the question request to the quetion service
const questionResponse = await axios.get(questionServiceUrl+'/questions/' + lang + '/' + amount + '/' + type);

res.json(questionResponse.data);

app.get('/questions/:lang/:amount/:type', verifyToken, async (req, res) => {
try {
if(!validateLang(req.params.lang.toString()) ||
!validateAmount(req.params.amount.toString()) ||
!validateType(req.params.type.toString()))
res.status(400).json({ error: 'Wrong values given' });
else {
const lang = encodeURIComponent(req.params.lang.toString());
const amount = encodeURIComponent(req.params.amount.toString());
const type = encodeURIComponent(req.params.type.toString());
// Forward the question request to the quetion service
const questionResponse = await axios.get(questionServiceUrl+'/questions/' + lang + '/' + amount + '/' + type);

res.json(questionResponse.data);
}
} catch (error) {

res.status(error.response.status).json({ error: error.response.data.error });
manageError(error)
}
});


app.get('/questions/:lang/:amount', async (req, res) => {
app.get('/questions/:lang/:amount', verifyToken, async (req, res) => {
try {
const lang = req.params.lang.toString();
const amount = req.params.amount.toString();
// Forward the question request to the quetion service
const questionResponse = await axios.get(questionServiceUrl+'/questions/' + lang + '/' + amount);

res.json(questionResponse.data);
if(!validateLang(req.params.lang.toString()) ||
!validateAmount(req.params.amount.toString()))
res.status(400).json({ error: 'Wrong values given' });
else{
const lang = encodeURIComponent(req.params.lang.toString());
const amount = encodeURIComponent(req.params.amount.toString());
// Forward the question request to the quetion service
const questionResponse = await axios.get(questionServiceUrl+'/questions/' + lang + '/' + amount);

res.json(questionResponse.data);
}
} catch (error) {

res.status(error.response.status).json({ error: error.response.data.error });
manageError(error)
}
});

app.get('/questions/:lang', async (req, res) => {
app.get('/questions/:lang', verifyToken, async (req, res) => {
try {
const lang = req.params.lang.toString();
// Forward the question request to the quetion service
const questionResponse = await axios.get(questionServiceUrl+'/questions/' + lang);

res.json(questionResponse.data);
if(!validateLang(req.params.lang.toString()))
res.status(400).json({ error: 'Wrong values given' });
else{
const lang = encodeURIComponent(req.params.lang.toString());
// Forward the question request to the quetion service
const questionResponse = await axios.get(questionServiceUrl+'/questions/' + lang.toString());

res.json(questionResponse.data);
}

} catch (error) {

res.status(error.response.status).json({ error: error.response.data.error });
manageError(error)
}
});

app.post('/record', async(req, res) => {
app.post('/record', verifyToken, async(req, res) => {

try {
// Forward the record request to the record service
const recordResponse = await axios.post(recordServiceUrl+'/record', req.body);
res.json(recordResponse.data);
} catch (error) {
res.send(error);
manageError(error)
}
});

app.get('/record/ranking/top10', async(req, res)=>{
app.get('/record/ranking/top10', verifyToken, async(req, res)=>{
try {
// Forward the record request to the record service
const recordResponse = await axios.get(recordServiceUrl + '/record/ranking/top10');
res.json(recordResponse.data);
} catch (error) {
res.send(error);
manageError(error)
}
});

app.get('/record/ranking/:user', async(req, res)=>{
app.get('/record/ranking/:user', verifyToken, async(req, res)=>{
try {
const user = req.params.user;
// Forward the record request to the record service
const recordResponse = await axios.get(recordServiceUrl + '/record/ranking/' + user);
res.json(recordResponse.data);
if(!validateUser(req.params.user.toString()))
res.status(400).json({ error: 'Wrong values given' });
else{
const user = encodeURIComponent(req.params.user.toString());
// Forward the record request to the record service
const recordResponse = await axios.get(recordServiceUrl + '/record/ranking/' + user);
res.json(recordResponse.data);
}
} catch (error) {
res.send(error);
manageError(error)
}
});

app.get('/record/:user', async(req, res)=>{
app.get('/record/:user', verifyToken, async(req, res)=>{
try {
const user = req.params.user;
// Forward the record request to the record service
const recordResponse = await axios.get(recordServiceUrl + '/record/' + user);
res.json(recordResponse.data);
if(!validateUser(req.params.user.toString()))
res.status(400).json({ error: 'Wrong values given' });
else{
const user = encodeURIComponent(req.params.user.toString());
// Forward the record request to the record service
const recordResponse = await axios.get(recordServiceUrl + '/record/' + user);
res.json(recordResponse.data);
}
} catch (error) {
res.send(error);
manageError(error)
}
});

Expand All @@ -159,4 +186,48 @@ const server = app.listen(port, () => {
console.log(`Gateway Service listening at http://localhost:${port}`);
});

function verifyToken(req, res, next) {
// Get the token from the request headers
const token = req.headers['token'] || req.body.token || req.query.token;

// Verify if the token is valid
jwt.verify(token, (process.env.JWT_KEY??'my-key'), (err, decoded) => {
if (err) {
// Token is not valid
res.status(403).json({authorized: false,
error: 'Invalid token or outdated'});
} else {
// Token is valid
req.decodedToken = decoded;
// Call next() to proceed to the next middleware or route handler
next();
}
});
}

function validateLang(lang){
return ['en', 'es', 'tk'].includes(lang);
}

function validateAmount(amount) {
const parsed = parseInt(amount, 10);
// We only accept integers and positive ones
return !isNaN(parsed) && parsed > 0;
}

function validateType(type){
return ['POPULATION', 'CAPITAL', 'LANGUAGE', 'SIZE'].includes(type);
}

function validateUser(user){
return !(/\s/.test(user)) //True if there are no spaces
}

function manageError(error){
if(error.response) //Some microservice responded with an error
res.status(error.response.status).json({ error: error.response.data.error });
else //Some other error
res.status(500).json({error : "Interanl server error"})
}

module.exports = server
34 changes: 25 additions & 9 deletions gatewayservice/gateway-service.test.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
const request = require('supertest');
const axios = require('axios');
const jwt = require('jsonwebtoken');
const app = require('./gateway-service');

afterAll(async () => {
app.close();
});


jest.mock('jsonwebtoken');

jest.mock('axios');

describe('Gateway Service', () => {


describe('Gateway Service with token mock', () => {

// Mock responses from external services
axios.post.mockImplementation((url, data) => {
if (url.endsWith('/login')) {
Expand Down Expand Up @@ -45,6 +52,14 @@ describe('Gateway Service', () => {
}
});



// Mock the `verify` function of JWT
jwt.verify.mockImplementation((token, secretOrPublicKey, callback) => {
// Assume the token is valid and return the payload
callback(null, "decoded");
});

// Test /login endpoint
it('should forward login request to auth service', async () => {
const response = await request(app)
Expand All @@ -69,39 +84,39 @@ describe('Gateway Service', () => {
// Test /questions endpoint
it('should forward questions request to question service', async () => {
const response = await request(app)
.get('/questions');
.get('/questions').set('token', 'valorDelToken');

checkQuestion(response);
});

// Test /questions/:lang endpoint
it('should forward questions request to question service', async () => {
const response = await request(app)
.get('/questions/es');
.get('/questions/es').set('token', 'valorDelToken');

checkQuestion(response);
});

// Test /questions/:lang/:amount endpoint
it('should forward questions request to question service', async () => {
const response = await request(app)
.get('/questions/es/1');
.get('/questions/es/1').set('token', 'valorDelToken');

checkQuestion(response);
});

// Test /questions/:lang/:amount/:type endpoint
it('should forward questions request to question service', async () => {
const response = await request(app)
.get('/questions/es/1/CAPITAL');
.get('/questions/es/1/CAPITAL').set('token', 'valorDelToken');

checkQuestion(response);
});

// Test /record endpoint
it('should forward record request to record service', async () => {
const response = await request(app)
.post('/record');
.post('/record').set('token', 'valorDelToken');

expect(response.statusCode).toBe(200);
expect(response.body.user).toBe('testuser');
Expand All @@ -110,26 +125,27 @@ describe('Gateway Service', () => {
// Test /record/:user endpoint
it('should forward record request to record service', async () => {
const response = await request(app)
.get('/record/testuser');
.get('/record/testuser').set('token', 'valorDelToken');

checkRecord(response);
});

// Test /record/ranking/:user endpoint
it('should forward record request to record service', async () => {
const response = await request(app)
.get('/record/ranking/testuser');
.get('/record/ranking/testuser').set('token', 'valorDelToken');

checkRecord(response);
});

// Test /record/ranking/top10 endpoint
it('should forward record request to record service', async () => {
const response = await request(app)
.get('/record/ranking/top10');
.get('/record/ranking/top10').set('token', 'valorDelToken');
checkRecord(response);

});

});

function checkRecord(response){
Expand Down
Loading