Added adminOnly auth option and auth middleware tests
This commit is contained in:
parent
c4d1490d01
commit
4406ec8e15
|
@ -188,13 +188,13 @@
|
||||||
"webpack": "1.13.2"
|
"webpack": "1.13.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"babel-jest": "^20.0.0",
|
"babel-jest": "22",
|
||||||
"enzyme": "2.8.2",
|
"enzyme": "2.8.2",
|
||||||
"enzyme-to-json": "^1.5.1",
|
"enzyme-to-json": "^1.5.1",
|
||||||
"fetch-test-server": "^1.1.0",
|
"fetch-test-server": "^1.1.0",
|
||||||
"flow-bin": "^0.49.1",
|
"flow-bin": "^0.49.1",
|
||||||
"identity-obj-proxy": "^3.0.0",
|
"identity-obj-proxy": "^3.0.0",
|
||||||
"jest-cli": "^20.0.0",
|
"jest-cli": "22",
|
||||||
"koa-webpack-dev-middleware": "1.4.5",
|
"koa-webpack-dev-middleware": "1.4.5",
|
||||||
"koa-webpack-hot-middleware": "1.0.3",
|
"koa-webpack-hot-middleware": "1.0.3",
|
||||||
"lint-staged": "^3.4.0",
|
"lint-staged": "^3.4.0",
|
||||||
|
@ -203,4 +203,4 @@
|
||||||
"raf": "^3.4.0",
|
"raf": "^3.4.0",
|
||||||
"react-test-renderer": "^16.1.0"
|
"react-test-renderer": "^16.1.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -5,7 +5,17 @@ import { type Context } from 'koa';
|
||||||
|
|
||||||
import { User, ApiKey } from '../../models';
|
import { User, ApiKey } from '../../models';
|
||||||
|
|
||||||
export default function auth({ require = true }: { require?: boolean } = {}) {
|
type AuthOptions = {
|
||||||
|
require?: boolean,
|
||||||
|
adminOnly?: boolean,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function auth(options: AuthOptions = {}) {
|
||||||
|
options = {
|
||||||
|
require: true,
|
||||||
|
...options,
|
||||||
|
};
|
||||||
|
|
||||||
return async function authMiddleware(
|
return async function authMiddleware(
|
||||||
ctx: Context,
|
ctx: Context,
|
||||||
next: () => Promise<void>
|
next: () => Promise<void>
|
||||||
|
@ -25,8 +35,7 @@ export default function auth({ require = true }: { require?: boolean } = {}) {
|
||||||
} else {
|
} else {
|
||||||
if (require) {
|
if (require) {
|
||||||
throw httpErrors.Unauthorized(
|
throw httpErrors.Unauthorized(
|
||||||
`Bad Authorization header format. \
|
`Bad Authorization header format. Format is "Authorization: Bearer <token>"`
|
||||||
Format is "Authorization: Bearer <token>"\n`
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -57,13 +66,13 @@ export default function auth({ require = true }: { require?: boolean } = {}) {
|
||||||
throw httpErrors.Unauthorized('Invalid API key');
|
throw httpErrors.Unauthorized('Invalid API key');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!apiKey) throw httpErrors.Unauthorized('Invalid token');
|
if (!apiKey) throw httpErrors.Unauthorized('Invalid API key');
|
||||||
|
|
||||||
user = await User.findOne({
|
user = await User.findOne({
|
||||||
where: { id: apiKey.userId },
|
where: { id: apiKey.userId },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!user) throw httpErrors.Unauthorized('Invalid token');
|
if (!user) throw httpErrors.Unauthorized('Invalid API key');
|
||||||
} else {
|
} else {
|
||||||
// JWT
|
// JWT
|
||||||
// Get user without verifying payload signature
|
// Get user without verifying payload signature
|
||||||
|
@ -87,6 +96,10 @@ export default function auth({ require = true }: { require?: boolean } = {}) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options.adminOnly && !user.isAdmin) {
|
||||||
|
throw httpErrors.Forbidden('Only available for admins');
|
||||||
|
}
|
||||||
|
|
||||||
ctx.state.token = token;
|
ctx.state.token = token;
|
||||||
ctx.state.user = user;
|
ctx.state.user = user;
|
||||||
// $FlowFixMe
|
// $FlowFixMe
|
||||||
|
|
|
@ -0,0 +1,197 @@
|
||||||
|
/* eslint-disable flowtype/require-valid-file-annotation */
|
||||||
|
import { flushdb, seed } from '../../test/support';
|
||||||
|
import ApiKey from '../../models/ApiKey';
|
||||||
|
import randomstring from 'randomstring';
|
||||||
|
import auth from './authentication';
|
||||||
|
|
||||||
|
beforeEach(flushdb);
|
||||||
|
|
||||||
|
describe('Authentication middleware', async () => {
|
||||||
|
describe('with JWT', () => {
|
||||||
|
it('should authenticate with correct token', async () => {
|
||||||
|
const state = {};
|
||||||
|
const { user } = await seed();
|
||||||
|
const authMiddleware = auth();
|
||||||
|
|
||||||
|
await authMiddleware(
|
||||||
|
{
|
||||||
|
request: {
|
||||||
|
get: jest.fn(() => `Bearer ${user.getJwtToken()}`),
|
||||||
|
},
|
||||||
|
state,
|
||||||
|
cache: {},
|
||||||
|
},
|
||||||
|
jest.fn()
|
||||||
|
);
|
||||||
|
expect(state.user.id).toEqual(user.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return error with invalid token', async () => {
|
||||||
|
const state = {};
|
||||||
|
const { user } = await seed();
|
||||||
|
const authMiddleware = auth();
|
||||||
|
|
||||||
|
try {
|
||||||
|
await authMiddleware(
|
||||||
|
{
|
||||||
|
request: {
|
||||||
|
get: jest.fn(() => `Bearer ${user.getJwtToken()}error`),
|
||||||
|
},
|
||||||
|
state,
|
||||||
|
cache: {},
|
||||||
|
},
|
||||||
|
jest.fn()
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
expect(e.message).toBe('Invalid token');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('with API key', () => {
|
||||||
|
it('should authenticate user with valid API key', async () => {
|
||||||
|
const state = {};
|
||||||
|
const { user } = await seed();
|
||||||
|
const authMiddleware = auth();
|
||||||
|
const key = await ApiKey.create({
|
||||||
|
userId: user.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
await authMiddleware(
|
||||||
|
{
|
||||||
|
request: {
|
||||||
|
get: jest.fn(() => `Bearer ${key.secret}`),
|
||||||
|
},
|
||||||
|
state,
|
||||||
|
cache: {},
|
||||||
|
},
|
||||||
|
jest.fn()
|
||||||
|
);
|
||||||
|
expect(state.user.id).toEqual(user.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return error with invalid API key', async () => {
|
||||||
|
const state = {};
|
||||||
|
const authMiddleware = auth();
|
||||||
|
|
||||||
|
try {
|
||||||
|
await authMiddleware(
|
||||||
|
{
|
||||||
|
request: {
|
||||||
|
get: jest.fn(() => `Bearer ${randomstring.generate(38)}`),
|
||||||
|
},
|
||||||
|
state,
|
||||||
|
cache: {},
|
||||||
|
},
|
||||||
|
jest.fn()
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
expect(e.message).toBe('Invalid API key');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('adminOnly', () => {
|
||||||
|
it('should work if user is an admin', async () => {
|
||||||
|
const state = {};
|
||||||
|
const { user } = await seed();
|
||||||
|
const authMiddleware = auth({ adminOnly: true });
|
||||||
|
user.isAdmin = true;
|
||||||
|
await user.save();
|
||||||
|
|
||||||
|
await authMiddleware(
|
||||||
|
{
|
||||||
|
request: {
|
||||||
|
get: jest.fn(() => `Bearer ${user.getJwtToken()}`),
|
||||||
|
},
|
||||||
|
state,
|
||||||
|
cache: {},
|
||||||
|
},
|
||||||
|
jest.fn()
|
||||||
|
);
|
||||||
|
expect(state.user.id).toEqual(user.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should raise 403 if user is not an admin', async () => {
|
||||||
|
const { user } = await seed();
|
||||||
|
const authMiddleware = auth({ adminOnly: true });
|
||||||
|
user.idAdmin = true;
|
||||||
|
await user.save();
|
||||||
|
|
||||||
|
try {
|
||||||
|
await authMiddleware({
|
||||||
|
request: {
|
||||||
|
get: jest.fn(() => `Bearer ${user.getJwtToken()}`),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
expect(e.message).toBe('Only available for admins');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return error message if no auth token is available', async () => {
|
||||||
|
const state = {};
|
||||||
|
const authMiddleware = auth();
|
||||||
|
|
||||||
|
try {
|
||||||
|
await authMiddleware(
|
||||||
|
{
|
||||||
|
request: {
|
||||||
|
get: jest.fn(() => 'error'),
|
||||||
|
},
|
||||||
|
state,
|
||||||
|
cache: {},
|
||||||
|
},
|
||||||
|
jest.fn()
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
expect(e.message).toBe(
|
||||||
|
'Bad Authorization header format. Format is "Authorization: Bearer <token>"'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should allow passing auth token as a GET param', async () => {
|
||||||
|
const state = {};
|
||||||
|
const { user } = await seed();
|
||||||
|
const authMiddleware = auth();
|
||||||
|
|
||||||
|
await authMiddleware(
|
||||||
|
{
|
||||||
|
request: {
|
||||||
|
get: jest.fn(() => null),
|
||||||
|
query: {
|
||||||
|
token: user.getJwtToken(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
body: {},
|
||||||
|
state,
|
||||||
|
cache: {},
|
||||||
|
},
|
||||||
|
jest.fn()
|
||||||
|
);
|
||||||
|
expect(state.user.id).toEqual(user.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should allow passing auth token in body params', async () => {
|
||||||
|
const state = {};
|
||||||
|
const { user } = await seed();
|
||||||
|
const authMiddleware = auth();
|
||||||
|
|
||||||
|
await authMiddleware(
|
||||||
|
{
|
||||||
|
request: {
|
||||||
|
get: jest.fn(() => null),
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
token: user.getJwtToken(),
|
||||||
|
},
|
||||||
|
state,
|
||||||
|
cache: {},
|
||||||
|
},
|
||||||
|
jest.fn()
|
||||||
|
);
|
||||||
|
expect(state.user.id).toEqual(user.id);
|
||||||
|
});
|
||||||
|
});
|
Reference in New Issue