fix(api): JSON web token tests and fixes.

This commit is contained in:
Sam Tolmay 2021-10-19 10:59:34 +02:00
parent cd82729c5d
commit 30f7267c3e
6 changed files with 372 additions and 4 deletions

View File

@ -0,0 +1,89 @@
/*
Copyright 2020-2021 Lowdefy, Inc
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import jwt from 'jsonwebtoken';
import testContext from '../../test/testContext';
import issueAccessToken from './issueAccessToken';
const secrets = {
JWT_SECRET: 'JWT_SECRET',
};
const RealDate = Date.now;
const mockNow = jest.fn();
mockNow.mockImplementation(() => 1000);
beforeEach(() => {
global.Date.now = mockNow;
});
afterAll(() => {
global.Date.now = RealDate;
});
test('issueAccessToken', () => {
const accessToken = issueAccessToken(testContext({ host: 'host', secrets }), {
claims: { sub: 'sub', email: 'email' },
});
const claims = jwt.verify(accessToken, 'JWT_SECRET', {
algorithms: ['HS256'],
audience: 'host',
issuer: 'host',
});
expect(claims).toEqual({
aud: 'host',
email: 'email',
exp: 14401, // 4 hours
iat: 1,
iss: 'host',
sub: 'sub',
lowdefy_access_token: true,
});
});
test('issueAccessToken, configure token expiry', () => {
const accessToken = issueAccessToken(
testContext({
config: {
auth: {
jwt: {
expiresIn: '12h',
},
},
},
host: 'host',
secrets,
}),
{ claims: { sub: 'sub', email: 'email' } }
);
const claims = jwt.verify(accessToken, 'JWT_SECRET', {
algorithms: ['HS256'],
audience: 'host',
issuer: 'host',
});
expect(claims).toEqual({
aud: 'host',
email: 'email',
exp: 43201, // 12 hours
iat: 1,
iss: 'host',
sub: 'sub',
lowdefy_access_token: true,
});
});

View File

@ -0,0 +1,92 @@
/*
Copyright 2020-2021 Lowdefy, Inc
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/*
Copyright 2020-2021 Lowdefy, Inc
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import jwt from 'jsonwebtoken';
import testContext from '../../test/testContext';
import issueOpenIdStateToken from './issueOpenIdStateToken';
const secrets = {
JWT_SECRET: 'JWT_SECRET',
};
const RealDate = Date.now;
const mockNow = jest.fn();
mockNow.mockImplementation(() => 1000);
beforeEach(() => {
global.Date.now = mockNow;
});
afterAll(() => {
global.Date.now = RealDate;
});
test('issueOpenIdStateToken', async () => {
const stateToken = issueOpenIdStateToken(testContext({ host: 'host', secrets }), {
input: { i: true },
pageId: 'pageId',
urlQuery: { u: true },
});
const claims = jwt.verify(stateToken, 'JWT_SECRET', {
algorithms: ['HS256'],
audience: 'host',
issuer: 'host',
});
expect(claims).toEqual({
aud: 'host',
exp: 301, // 5min
iat: 1,
input: { i: true },
iss: 'host',
lowdefy_openid_state_token: true,
pageId: 'pageId',
urlQuery: { u: true },
});
});
test('issueOpenIdStateToken, no location data', async () => {
const stateToken = issueOpenIdStateToken(testContext({ host: 'host', secrets }), {});
const claims = jwt.verify(stateToken, 'JWT_SECRET', {
algorithms: ['HS256'],
audience: 'host',
issuer: 'host',
});
expect(claims).toEqual({
aud: 'host',
exp: 301, // 5min
iat: 1,
iss: 'host',
lowdefy_openid_state_token: true,
});
});

View File

@ -0,0 +1,97 @@
/*
Copyright 2020-2021 Lowdefy, Inc
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import jwt from 'jsonwebtoken';
import testContext from '../../test/testContext';
import issueAccessToken from './issueAccessToken';
import issueOpenIdStateToken from './issueOpenIdStateToken';
import verifyAccessToken from './verifyAccessToken';
import { AuthenticationError, TokenExpiredError } from '../../context/errors';
const secrets = {
JWT_SECRET: 'JWT_SECRET',
};
const context = testContext({ host: 'host', secrets });
const RealDate = Date.now;
const mockNow = jest.fn();
mockNow.mockImplementation(() => 1000);
beforeEach(() => {
global.Date.now = mockNow;
});
afterAll(() => {
global.Date.now = RealDate;
});
test('verifyAccessToken', () => {
const token = issueAccessToken(context, {
claims: { sub: 'sub', email: 'email' },
});
const claims = verifyAccessToken(context, { token });
expect(claims).toEqual({
aud: 'host',
email: 'email',
exp: 14401, // 4 hours
iat: 1,
iss: 'host',
sub: 'sub',
lowdefy_access_token: true,
});
});
test('verifyAccessToken openIdState token invalid', () => {
const token = issueOpenIdStateToken(context, {
input: { i: true },
pageId: 'pageId',
urlQuery: { u: true },
});
expect(() => verifyAccessToken(context, { token })).toThrow(AuthenticationError);
});
test('verifyAccessToken invalid token', () => {
expect(() => verifyAccessToken(context, { token: 'not a token' })).toThrow(AuthenticationError);
});
test('verifyAccessToken no sub invalid token', () => {
const token = issueAccessToken(context, {
claims: {
email: 'email',
},
});
expect(() => verifyAccessToken(context, { token })).toThrow(AuthenticationError);
});
test('verifyAccessToken token expired', () => {
const token = jwt.sign(
{
sub: 'sub',
lowdefy_access_token: true,
exp: -10000,
},
'JWT_SECRET',
{
audience: 'host',
issuer: 'host',
}
);
expect(() => verifyAccessToken(context, { token })).toThrow(TokenExpiredError);
});

View File

@ -15,7 +15,7 @@
*/
import jwt from 'jsonwebtoken';
import { AuthenticationError, TokenExpiredError } from '../../context/errors';
import { AuthenticationError } from '../../context/errors';
function verifyOpenIdStateToken({ host, secrets }, { token }) {
try {
@ -31,7 +31,7 @@ function verifyOpenIdStateToken({ host, secrets }, { token }) {
return claims;
} catch (err) {
if (err.name === 'TokenExpiredError') {
throw new TokenExpiredError('Token expired.');
throw new AuthenticationError('Token expired.');
} else {
throw new AuthenticationError('Invalid token.');
}

View File

@ -0,0 +1,90 @@
/*
Copyright 2020-2021 Lowdefy, Inc
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import jwt from 'jsonwebtoken';
import testContext from '../../test/testContext';
import issueAccessToken from './issueAccessToken';
import issueOpenIdStateToken from './issueOpenIdStateToken';
import verifyOpenIdStateToken from './verifyOpenIdStateToken';
import { AuthenticationError } from '../../context/errors';
const secrets = {
JWT_SECRET: 'JWT_SECRET',
};
const context = testContext({ host: 'host', secrets });
const RealDate = Date.now;
const mockNow = jest.fn();
mockNow.mockImplementation(() => 1000);
beforeEach(() => {
global.Date.now = mockNow;
});
afterAll(() => {
global.Date.now = RealDate;
});
test('verifyOpenIdStateToken', () => {
const token = issueOpenIdStateToken(context, {
input: { i: true },
pageId: 'pageId',
urlQuery: { u: true },
});
const claims = verifyOpenIdStateToken(context, { token });
expect(claims).toEqual({
aud: 'host',
exp: 301, // 5min
iat: 1,
input: { i: true },
iss: 'host',
lowdefy_openid_state_token: true,
pageId: 'pageId',
urlQuery: { u: true },
});
});
test('verifyOpenIdStateToken access token invalid', () => {
const token = issueAccessToken(context, {
claims: { sub: 'sub', email: 'email' },
});
expect(() => verifyOpenIdStateToken(context, { token })).toThrow(AuthenticationError);
});
test('verifyOpenIdStateToken invalid token', () => {
expect(() => verifyOpenIdStateToken(context, { token: 'not a token' })).toThrow(
AuthenticationError
);
});
test('verifyOpenIdStateToken token expired', () => {
const token = jwt.sign(
{
lowdefy_openid_state_token: true,
exp: -10000,
},
'JWT_SECRET',
{
audience: 'host',
issuer: 'host',
}
);
expect(() => verifyOpenIdStateToken(context, { token })).toThrow(AuthenticationError);
});

View File

@ -28,8 +28,8 @@ async function run() {
// configDirectory: path.resolve(process.cwd(), '../docs'),
// configDirectory: path.resolve(process.cwd(), '../servers/serverDev'),
configDirectory: process.cwd(),
// outputDirectory: path.resolve(process.cwd(), '../servers/serverDev/.lowdefy/build'),
outputDirectory: path.resolve(process.cwd(), './.lowdefy/build'),
outputDirectory: path.resolve(process.cwd(), '../servers/serverDev/.lowdefy/build'),
// outputDirectory: path.resolve(process.cwd(), './.lowdefy/build'),
});
}