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
16 changes: 11 additions & 5 deletions src/exceptions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,21 @@ export class FeatureUnavailableError extends Error {
}

export function responseException(res, err, code, feature = undefined) {
if (process.env.SWITCHER_API_LOGGER == 'true' && feature) {
Logger.info(`Feature [${feature}]`, { log: Switcher.getLogger(feature) });
}

responseExceptionSilent(res, err, code, err.message);
}

export function responseExceptionSilent(res, err, code, message) {
if (process.env.SWITCHER_API_LOGGER == 'true') {
Logger.httpError(err.constructor.name, err.code, err.message, err);
if (feature) {
Logger.info(`Feature [${feature}]`, { log: Switcher.getLogger(feature) });
}
}

if (err.code) {
return res.status(err.code).send({ error: err.message });
return res.status(err.code).send({ error: message });
}
res.status(code).send({ error: err.message });

res.status(code).send({ error: message });
}
41 changes: 19 additions & 22 deletions src/middleware/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,27 @@ import { getComponentById } from '../services/component';
import Admin from '../models/admin';
import Component from '../models/component';
import { getRateLimit } from '../external/switcher-api-facade';
import { responseExceptionSilent } from '../exceptions';

export async function auth(req, res, next) {
try {
const token = req.header('Authorization').replace('Bearer ', '');
const token = req.header('Authorization')?.replace('Bearer ', '');
const decoded = jwt.verify(token, process.env.JWT_SECRET);
const admin = await getAdminById(decoded._id);

if (!admin || !admin.active) {
throw new Error();
if (!admin?.active) {
throw new Error('User not active');
}

if (admin.token !== Admin.extractTokenPart(token)) {
throw new Error();
throw new Error(`Invalid token for ${admin.email}`);
}

req.token = token;
req.admin = admin;
next();
} catch (e) {
res.status(401).send({ error: 'Please authenticate.' });
} catch (err) {
responseExceptionSilent(res, err, 401, 'Please authenticate.');
}
}

Expand All @@ -35,21 +36,21 @@ export async function authRefreshToken(req, res, next) {

const decodedRefreshToken = jwt.verify(refreshToken, process.env.JWT_SECRET);
if (decodedRefreshToken.subject !== Admin.extractTokenPart(token)) {
throw new Error();
throw new Error('Refresh code does not match');
}

const decoded = await jwt.decode(token);
const admin = await getAdmin({ _id: decoded._id, token: decodedRefreshToken.subject });

if (!admin || !admin.active) {
throw new Error();
if (!admin?.active) {
throw new Error('User not active');
}

const newTokenPair = await admin.generateAuthToken();
res.jwt = newTokenPair;
next();
} catch (e) {
res.status(401).send({ error: 'Unable to refresh token.' });
} catch (err) {
responseExceptionSilent(res, err, 401, 'Unable to refresh token.');
}
}

Expand All @@ -60,7 +61,7 @@ export async function appAuth(req, res, next) {
const component = await getComponentById(decoded.component);

if (component?.apihash.substring(50, component.apihash.length - 1) !== decoded.vc) {
throw new Error();
throw new Error('Invalid API token');
}

req.token = token;
Expand All @@ -70,8 +71,8 @@ export async function appAuth(req, res, next) {
req.environment = decoded.environment;
req.rate_limit = decoded.rate_limit;
next();
} catch (e) {
res.status(401).send({ error: 'Invalid API token.' });
} catch (err) {
responseExceptionSilent(res, err, 401, 'Invalid API token.');
}
}

Expand All @@ -80,8 +81,8 @@ export async function slackAuth(req, res, next) {
const token = req.header('Authorization').replace('Bearer ', '');
jwt.verify(token, process.env.SWITCHER_SLACK_JWT_SECRET);
next();
} catch (e) {
res.status(401).send({ error: 'Invalid API token.' });
} catch (err) {
responseExceptionSilent(res, err, 401, 'Invalid API token.');
}
}

Expand All @@ -99,10 +100,6 @@ export async function appGenerateCredentials(req, res, next) {
const key = req.header('switcher-api-key');
const { component, domain } = await Component.findByCredentials(req.body.domain, req.body.component, key);

if (!component) {
throw new Error();
}

const rate_limit = await getRateLimit(key, component);
const token = await component.generateAuthToken(req.body.environment, rate_limit);

Expand All @@ -111,7 +108,7 @@ export async function appGenerateCredentials(req, res, next) {
req.environment = req.body.environment;
req.rate_limit = rate_limit;
next();
} catch (e) {
res.status(401).send({ error: 'Invalid token request' });
} catch (err) {
responseExceptionSilent(res, err, 401, 'Invalid token request.');
}
}
6 changes: 3 additions & 3 deletions src/services/metric.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,11 @@ export async function getStatistics(req) {
let result = {};
const options = String(req.query.statistics);
await Promise.all([
options.match(/(switchers)|(all)/) ?
RegExp(/(switchers)|(all)/).exec(options) ?
aggregateSwitchers(switcher.aggregate, dateGroupPattern, result) : Promise.resolve(),
options.match(/(components)|(all)/) ?
RegExp(/(components)|(all)/).exec(options) ?
aggregateComponents(components.aggregate, dateGroupPattern, result) : Promise.resolve(),
options.match(/(reasons)|(all)/) ?
RegExp(/(reasons)|(all)/).exec(options) ?
aggreagateReasons(reasons.aggregate, result) : Promise.resolve()
]);

Expand Down
26 changes: 26 additions & 0 deletions tests/admin.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,32 @@ describe('Testing Admin insertion', () => {
}).expect(200);
});

test('ADMIN_SUITE - Should NOT access routes - Admin not active', async () => {
// given
let admin = await Admin.findById(signedupUser).exec();
let response = await request(app)
.post('/admin/login')
.send({
email: admin.email,
password: '12312312312'
}).expect(200);

// admin not active
admin.active = false;
await admin.save();
const token = response.body.jwt.token;

// test
await request(app)
.get('/admin/me')
.set('Authorization', `Bearer ${token}`)
.send().expect(401);

// teardown - restore admin
admin.active = true;
await admin.save();
});

test('ADMIN_SUITE - Should NOT confirm access to a new Admin - Invalid access code', async () => {
await request(app)
.post('/admin/signup/authorization?code=INVALID_CODE')
Expand Down
13 changes: 13 additions & 0 deletions tests/metric.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,19 @@ describe('Fetch overall statistics', () => {
expect(response.body?.reasons).toEqual(undefined);
});

test('METRIC_SUITE - Should return only reason statistics from a given Domain', async () => {
const response = await request(app)
.get(`/metric/statistics?domainid=${domainId}&statistics=reasons`)
.set('Authorization', `Bearer ${adminMasterAccountToken}`)
.send().expect(200);

// Response validation
expect(response.body).not.toBeNull();
expect(response.body?.reasons.length > 0).toEqual(true);
expect(response.body?.switchers).toEqual(undefined);
expect(response.body?.components).toEqual(undefined);
});

test('METRIC_SUITE - Should return statistics filtered by Switcher KEY', async () => {
const response = await request(app)
.get(`/metric/statistics?domainid=${domainId}&key=KEY_1&statistics=all`)
Expand Down