mirror of
https://github.com/lowdefy/lowdefy.git
synced 2025-02-23 14:39:32 +08:00
feat(graphql): Update jwt tokens, add tests.
This commit is contained in:
parent
872ef2e387
commit
f5ea705074
@ -25,8 +25,8 @@ function createContext(config) {
|
||||
const bootstrapContext = {
|
||||
CONFIGURATION_BASE_PATH,
|
||||
development,
|
||||
logger,
|
||||
getSecrets,
|
||||
logger,
|
||||
};
|
||||
// lambda context function signature is ({ event }),
|
||||
// but express is ({ req })
|
||||
|
@ -25,7 +25,6 @@ const logger = {
|
||||
log: mockLog,
|
||||
};
|
||||
|
||||
const mockGetHeadersFromInput = jest.fn((input) => input.headers);
|
||||
const mockGetSecrets = jest.fn(() => ({}));
|
||||
|
||||
const config = {
|
||||
@ -34,6 +33,14 @@ const config = {
|
||||
getSecrets: mockGetSecrets,
|
||||
};
|
||||
|
||||
const input = {
|
||||
req: {
|
||||
headers: {
|
||||
host: 'host',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/* TODO:
|
||||
- secrets can only be accessed where they should be
|
||||
- CONFIGURATION_BASE_PATH is mapped to loaders
|
||||
@ -46,7 +53,7 @@ test('create context function', () => {
|
||||
|
||||
test('context function returns context object with getController and logger', async () => {
|
||||
const contextFn = createContext(config);
|
||||
const context = await contextFn();
|
||||
const context = await contextFn(input);
|
||||
expect(context).toBeInstanceOf(Object);
|
||||
expect(context.logger).toBe(logger);
|
||||
expect(context.getController).toBeInstanceOf(Function);
|
||||
@ -55,7 +62,7 @@ test('context function returns context object with getController and logger', as
|
||||
|
||||
test('context function returns context object with getController and logger', async () => {
|
||||
const contextFn = createContext(config);
|
||||
const context = await contextFn();
|
||||
const context = await contextFn(input);
|
||||
expect(context).toBeInstanceOf(Object);
|
||||
expect(context.logger).toBe(logger);
|
||||
expect(context.getController).toBeInstanceOf(Function);
|
||||
@ -63,7 +70,7 @@ test('context function returns context object with getController and logger', as
|
||||
|
||||
test('getController returns the correct controllers', async () => {
|
||||
const contextFn = createContext(config);
|
||||
const context = await contextFn();
|
||||
const context = await contextFn(input);
|
||||
const pageController = context.getController('page');
|
||||
expect(pageController).toBeInstanceOf(PageController);
|
||||
const componentController = context.getController('component');
|
||||
@ -72,14 +79,54 @@ test('getController returns the correct controllers', async () => {
|
||||
|
||||
test('logger is mapped through', async () => {
|
||||
const contextFn = createContext(config);
|
||||
const context = await contextFn();
|
||||
const context = await contextFn(input);
|
||||
context.logger.log('test');
|
||||
expect(mockLog.mock.calls).toEqual([['test']]);
|
||||
});
|
||||
|
||||
test('request controller has getSecrets', async () => {
|
||||
const contextFn = createContext(config);
|
||||
const context = await contextFn();
|
||||
const context = await contextFn(input);
|
||||
const requestController = context.getController('request');
|
||||
expect(requestController.getSecrets).toBe(mockGetSecrets);
|
||||
});
|
||||
|
||||
test('request controller get host from req', async () => {
|
||||
const contextFn = createContext(config);
|
||||
let context = await contextFn({
|
||||
req: {
|
||||
headers: {
|
||||
host: 'host',
|
||||
},
|
||||
},
|
||||
});
|
||||
let openIDController = context.getController('openId');
|
||||
expect(openIDController.host).toBe('host');
|
||||
context = await contextFn({
|
||||
req: {
|
||||
headers: {
|
||||
Host: 'host',
|
||||
},
|
||||
},
|
||||
});
|
||||
openIDController = context.getController('openId');
|
||||
expect(openIDController.host).toBe('host');
|
||||
context = await contextFn({
|
||||
event: {
|
||||
headers: {
|
||||
host: 'host',
|
||||
},
|
||||
},
|
||||
});
|
||||
openIDController = context.getController('openId');
|
||||
expect(openIDController.host).toBe('host');
|
||||
context = await contextFn({
|
||||
event: {
|
||||
headers: {
|
||||
Host: 'host',
|
||||
},
|
||||
},
|
||||
});
|
||||
openIDController = context.getController('openId');
|
||||
expect(openIDController.host).toBe('host');
|
||||
});
|
||||
|
@ -85,7 +85,9 @@ class OpenIdController {
|
||||
async callback({ code, state }) {
|
||||
try {
|
||||
const config = await this.getOpenIdConfig();
|
||||
if (!config) return null;
|
||||
if (!config) {
|
||||
throw new Error('OpenID Connect is not configured.');
|
||||
}
|
||||
|
||||
const { claims, idToken } = await this.openIdCallback({ code, config });
|
||||
|
||||
|
231
packages/graphql/src/controllers/openIdController.test.js
Normal file
231
packages/graphql/src/controllers/openIdController.test.js
Normal file
@ -0,0 +1,231 @@
|
||||
/*
|
||||
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 { Issuer } from 'openid-client';
|
||||
import { testBootstrapContext } from '../test/testContext';
|
||||
import createOpenIdController from './openIdController';
|
||||
import { AuthenticationError, ConfigurationError } from '../context/errors';
|
||||
|
||||
jest.mock('openid-client');
|
||||
|
||||
const mockopenIdAuthorizationUrl = jest.fn(
|
||||
// eslint-disable-next-line camelcase
|
||||
({ redirect_uri, response_type, scope, state }) =>
|
||||
`${redirect_uri}:${response_type}:${scope}:${state}`
|
||||
);
|
||||
|
||||
const mockClient = jest.fn(() => ({
|
||||
authorizationUrl: mockopenIdAuthorizationUrl,
|
||||
}));
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
Issuer.discover = jest.fn(() => ({
|
||||
Client: mockClient,
|
||||
}));
|
||||
|
||||
const secrets = {
|
||||
OPENID_CLIENT_ID: 'OPENID_CLIENT_ID',
|
||||
OPENID_CLIENT_SECRET: 'OPENID_CLIENT_SECRET',
|
||||
OPENID_DOMAIN: 'OPENID_DOMAIN',
|
||||
JWT_SECRET: 'JWT_SECRET',
|
||||
};
|
||||
|
||||
const mockLoadComponent = jest.fn();
|
||||
const loaders = {
|
||||
component: {
|
||||
load: mockLoadComponent,
|
||||
},
|
||||
};
|
||||
|
||||
const getSecrets = jest.fn();
|
||||
|
||||
const context = testBootstrapContext({ getSecrets, host: 'host', loaders });
|
||||
|
||||
const authorizationUrlInput = { input: { i: true }, pageId: 'pageId', urlQuery: { u: true } };
|
||||
const callbackInput = { code: 'code', state: 'state' };
|
||||
|
||||
const RealDate = Date.now;
|
||||
|
||||
const mockNow = jest.fn();
|
||||
mockNow.mockImplementation(() => 1000);
|
||||
|
||||
beforeEach(() => {
|
||||
mockLoadComponent.mockReset();
|
||||
getSecrets.mockReset();
|
||||
global.Date.now = mockNow;
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
global.Date.now = RealDate;
|
||||
});
|
||||
|
||||
describe('getOpenIdConfig', () => {
|
||||
test('getOpenIdConfig, no optional config', async () => {
|
||||
getSecrets.mockImplementation(() => secrets);
|
||||
const openIdController = createOpenIdController(context);
|
||||
const config = await openIdController.getOpenIdConfig();
|
||||
expect(config).toEqual({
|
||||
clientId: 'OPENID_CLIENT_ID',
|
||||
clientSecret: 'OPENID_CLIENT_SECRET',
|
||||
domain: 'OPENID_DOMAIN',
|
||||
});
|
||||
});
|
||||
|
||||
test('getOpenIdConfig, all optional config', async () => {
|
||||
getSecrets.mockImplementation(() => secrets);
|
||||
mockLoadComponent.mockImplementation(() => ({
|
||||
auth: {
|
||||
openId: {
|
||||
logoutFromProvider: true,
|
||||
logoutRedirectUri: 'logoutRedirectUri',
|
||||
scope: 'scope',
|
||||
},
|
||||
},
|
||||
}));
|
||||
const openIdController = createOpenIdController(context);
|
||||
const config = await openIdController.getOpenIdConfig();
|
||||
expect(config).toEqual({
|
||||
clientId: 'OPENID_CLIENT_ID',
|
||||
clientSecret: 'OPENID_CLIENT_SECRET',
|
||||
domain: 'OPENID_DOMAIN',
|
||||
logoutFromProvider: true,
|
||||
logoutRedirectUri: 'logoutRedirectUri',
|
||||
scope: 'scope',
|
||||
});
|
||||
});
|
||||
|
||||
test('getOpenIdConfig, no openId config', async () => {
|
||||
getSecrets.mockImplementation(() => ({}));
|
||||
const openIdController = createOpenIdController(context);
|
||||
const config = await openIdController.getOpenIdConfig();
|
||||
expect(config).toEqual(null);
|
||||
});
|
||||
|
||||
test('getOpenIdConfig, some openId config', async () => {
|
||||
const openIdController = createOpenIdController(context);
|
||||
|
||||
getSecrets.mockImplementationOnce(() => ({ OPENID_CLIENT_ID: 'OPENID_CLIENT_ID' }));
|
||||
await expect(openIdController.getOpenIdConfig()).rejects.toThrow(
|
||||
'Invalid OpenID Connect configuration.'
|
||||
);
|
||||
getSecrets.mockImplementationOnce(() => ({ OPENID_CLIENT_SECRET: 'OPENID_CLIENT_SECRET' }));
|
||||
await expect(openIdController.getOpenIdConfig()).rejects.toThrow(
|
||||
'Invalid OpenID Connect configuration.'
|
||||
);
|
||||
getSecrets.mockImplementationOnce(() => ({ OPENID_DOMAIN: 'OPENID_DOMAIN' }));
|
||||
await expect(openIdController.getOpenIdConfig()).rejects.toThrow(
|
||||
'Invalid OpenID Connect configuration.'
|
||||
);
|
||||
|
||||
getSecrets.mockImplementationOnce(() => ({
|
||||
OPENID_CLIENT_SECRET: 'OPENID_CLIENT_SECRET',
|
||||
OPENID_DOMAIN: 'OPENID_DOMAIN',
|
||||
}));
|
||||
await expect(openIdController.getOpenIdConfig()).rejects.toThrow(
|
||||
'Invalid OpenID Connect configuration.'
|
||||
);
|
||||
getSecrets.mockImplementationOnce(() => ({
|
||||
OPENID_CLIENT_ID: 'OPENID_CLIENT_ID',
|
||||
OPENID_DOMAIN: 'OPENID_DOMAIN',
|
||||
}));
|
||||
await expect(openIdController.getOpenIdConfig()).rejects.toThrow(
|
||||
'Invalid OpenID Connect configuration.'
|
||||
);
|
||||
getSecrets.mockImplementationOnce(() => ({
|
||||
OPENID_CLIENT_ID: 'OPENID_CLIENT_ID',
|
||||
OPENID_CLIENT_SECRET: 'OPENID_CLIENT_SECRET',
|
||||
}));
|
||||
await expect(openIdController.getOpenIdConfig()).rejects.toThrow(
|
||||
'Invalid OpenID Connect configuration.'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('authorizationUrl', () => {
|
||||
test('authorizationUrl, no openId config', async () => {
|
||||
getSecrets.mockImplementation(() => ({}));
|
||||
const openIdController = createOpenIdController(context);
|
||||
const url = await openIdController.authorizationUrl(authorizationUrlInput);
|
||||
expect(url).toEqual(null);
|
||||
});
|
||||
|
||||
test('authorizationUrl, no optional config', async () => {
|
||||
getSecrets.mockImplementation(() => secrets);
|
||||
const openIdController = createOpenIdController(context);
|
||||
const url = await openIdController.authorizationUrl(authorizationUrlInput);
|
||||
expect(mockClient.mock.calls).toEqual([
|
||||
[
|
||||
{
|
||||
client_id: 'OPENID_CLIENT_ID',
|
||||
client_secret: 'OPENID_CLIENT_SECRET',
|
||||
redirect_uris: ['https://host/auth/openid-callback'],
|
||||
},
|
||||
],
|
||||
]);
|
||||
expect(url).toEqual(
|
||||
'https://host/auth/openid-callback:code:openid profile email:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpbnB1dCI6eyJpIjp0cnVlfSwibG93ZGVmeV9vcGVuaWRfc3RhdGVfdG9rZW4iOnRydWUsInBhZ2VJZCI6InBhZ2VJZCIsInVybFF1ZXJ5Ijp7InUiOnRydWV9LCJpYXQiOjEsImV4cCI6MzAxLCJhdWQiOiJob3N0IiwiaXNzIjoiaG9zdCJ9.-GLdtCspyagMhdx9z1VootZXXbIdLY3cbzpn5UK8eGI'
|
||||
);
|
||||
});
|
||||
|
||||
test('authorizationUrl, set scope', async () => {
|
||||
getSecrets.mockImplementation(() => secrets);
|
||||
mockLoadComponent.mockImplementation(() => ({
|
||||
auth: {
|
||||
openId: {
|
||||
scope: 'custom scope',
|
||||
},
|
||||
},
|
||||
}));
|
||||
const openIdController = createOpenIdController(context);
|
||||
const url = await openIdController.authorizationUrl(authorizationUrlInput);
|
||||
expect(url).toEqual(
|
||||
'https://host/auth/openid-callback:code:custom scope:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpbnB1dCI6eyJpIjp0cnVlfSwibG93ZGVmeV9vcGVuaWRfc3RhdGVfdG9rZW4iOnRydWUsInBhZ2VJZCI6InBhZ2VJZCIsInVybFF1ZXJ5Ijp7InUiOnRydWV9LCJpYXQiOjEsImV4cCI6MzAxLCJhdWQiOiJob3N0IiwiaXNzIjoiaG9zdCJ9.-GLdtCspyagMhdx9z1VootZXXbIdLY3cbzpn5UK8eGI'
|
||||
);
|
||||
});
|
||||
|
||||
test('redirect Uris', async () => {
|
||||
const openIdController = createOpenIdController(context);
|
||||
expect(openIdController.redirectUri).toEqual('https://host/auth/openid-callback');
|
||||
|
||||
const devOpenIdController = createOpenIdController(
|
||||
testBootstrapContext({ getSecrets, host: 'host', loaders, development: true })
|
||||
);
|
||||
expect(devOpenIdController.redirectUri).toEqual('http://host/auth/openid-callback');
|
||||
});
|
||||
|
||||
test('authorizationUrl, jwt config error', async () => {
|
||||
getSecrets.mockImplementation(() => ({
|
||||
OPENID_CLIENT_ID: 'OPENID_CLIENT_ID',
|
||||
OPENID_CLIENT_SECRET: 'OPENID_CLIENT_SECRET',
|
||||
OPENID_DOMAIN: 'OPENID_DOMAIN',
|
||||
}));
|
||||
const openIdController = createOpenIdController(context);
|
||||
await expect(openIdController.authorizationUrl(authorizationUrlInput)).rejects.toThrow(
|
||||
ConfigurationError
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('callback', () => {
|
||||
test('callback, no openId config', async () => {
|
||||
getSecrets.mockImplementation(() => ({}));
|
||||
const openIdController = createOpenIdController(context);
|
||||
await expect(openIdController.callback(callbackInput)).rejects.toThrow(AuthenticationError);
|
||||
await expect(openIdController.callback(callbackInput)).rejects.toThrow(
|
||||
'OpenID Connect is not configured.'
|
||||
);
|
||||
});
|
||||
});
|
@ -18,19 +18,17 @@ import jwt from 'jsonwebtoken';
|
||||
import { AuthenticationError, TokenExpiredError } from '../context/errors';
|
||||
|
||||
class TokenController {
|
||||
constructor({ getLoader, getSecrets, host }) {
|
||||
this.componentLoader = getLoader('component');
|
||||
constructor({ getSecrets, host }) {
|
||||
this.host = host;
|
||||
this.getSecrets = getSecrets;
|
||||
}
|
||||
|
||||
async issueAccessToken({ sub, groups = [] }) {
|
||||
async issueAccessToken(claims) {
|
||||
const { JWT_SECRET } = await this.getSecrets();
|
||||
return jwt.sign(
|
||||
{
|
||||
sub,
|
||||
type: 'lowdefy_access',
|
||||
groups,
|
||||
...claims,
|
||||
lowdefy_access_token: true,
|
||||
},
|
||||
JWT_SECRET,
|
||||
{
|
||||
@ -50,7 +48,7 @@ class TokenController {
|
||||
issuer: this.host,
|
||||
});
|
||||
|
||||
if (claims.type !== 'lowdefy_access') {
|
||||
if (claims.lowdefy_access_token !== true) {
|
||||
throw new AuthenticationError('Invalid token');
|
||||
}
|
||||
if (!claims.sub) {
|
||||
@ -70,8 +68,8 @@ class TokenController {
|
||||
const { JWT_SECRET } = await this.getSecrets();
|
||||
return jwt.sign(
|
||||
{
|
||||
type: 'openid_state',
|
||||
input,
|
||||
lowdefy_openid_state_token: true,
|
||||
pageId,
|
||||
urlQuery,
|
||||
},
|
||||
@ -93,7 +91,7 @@ class TokenController {
|
||||
issuer: this.host,
|
||||
});
|
||||
|
||||
if (claims.type !== 'openid_state') {
|
||||
if (claims.lowdefy_openid_state_token !== true) {
|
||||
throw new AuthenticationError('Invalid token');
|
||||
}
|
||||
return claims;
|
||||
|
209
packages/graphql/src/controllers/tokenController.test.js
Normal file
209
packages/graphql/src/controllers/tokenController.test.js
Normal file
@ -0,0 +1,209 @@
|
||||
/*
|
||||
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 { testBootstrapContext } from '../test/testContext';
|
||||
import createTokenController from './tokenController';
|
||||
import { AuthenticationError, TokenExpiredError } from '../context/errors';
|
||||
|
||||
const secrets = {
|
||||
JWT_SECRET: 'JWT_SECRET',
|
||||
};
|
||||
|
||||
const getSecrets = () => secrets;
|
||||
|
||||
const context = testBootstrapContext({ getSecrets, host: 'host' });
|
||||
|
||||
const RealDate = Date.now;
|
||||
|
||||
const mockNow = jest.fn();
|
||||
mockNow.mockImplementation(() => 1000);
|
||||
|
||||
const openIdClaims = { sub: 'sub', email: 'email' };
|
||||
|
||||
const openIdStateLocation = { input: { i: true }, pageId: 'pageId', urlQuery: { u: true } };
|
||||
|
||||
beforeEach(() => {
|
||||
global.Date.now = mockNow;
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
global.Date.now = RealDate;
|
||||
});
|
||||
|
||||
describe('access tokens', () => {
|
||||
test('issueAccessToken', async () => {
|
||||
const tokenController = createTokenController(context);
|
||||
const accessToken = await tokenController.issueAccessToken(openIdClaims);
|
||||
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,
|
||||
});
|
||||
});
|
||||
|
||||
test('verifyAccessToken', async () => {
|
||||
const tokenController = createTokenController(context);
|
||||
const accessToken = await tokenController.issueAccessToken(openIdClaims);
|
||||
const claims = await tokenController.verifyAccessToken(accessToken);
|
||||
expect(claims).toEqual({
|
||||
aud: 'host',
|
||||
email: 'email',
|
||||
exp: 43201, // 12 hours
|
||||
iat: 1,
|
||||
iss: 'host',
|
||||
sub: 'sub',
|
||||
lowdefy_access_token: true,
|
||||
});
|
||||
});
|
||||
|
||||
test('verifyAccessToken openIdState token invalid', async () => {
|
||||
const tokenController = createTokenController(context);
|
||||
const token = await tokenController.issueOpenIdStateToken(openIdStateLocation);
|
||||
await expect(tokenController.verifyAccessToken(token)).rejects.toThrow(AuthenticationError);
|
||||
});
|
||||
|
||||
test('verifyAccessToken invalid token', async () => {
|
||||
const tokenController = createTokenController(context);
|
||||
await expect(tokenController.verifyAccessToken('not a token')).rejects.toThrow(
|
||||
AuthenticationError
|
||||
);
|
||||
});
|
||||
|
||||
test('verifyAccessToken no sub invalid token', async () => {
|
||||
const tokenController = createTokenController(context);
|
||||
const accessToken = await tokenController.issueAccessToken({
|
||||
email: 'email',
|
||||
});
|
||||
await expect(tokenController.verifyAccessToken(accessToken)).rejects.toThrow(
|
||||
AuthenticationError
|
||||
);
|
||||
});
|
||||
|
||||
test('verifyAccessToken token expired', async () => {
|
||||
const tokenController = createTokenController(context);
|
||||
const token = jwt.sign(
|
||||
{
|
||||
sub: 'sub',
|
||||
lowdefy_access_token: true,
|
||||
exp: -10000,
|
||||
},
|
||||
'JWT_SECRET',
|
||||
{
|
||||
audience: 'host',
|
||||
issuer: 'host',
|
||||
}
|
||||
);
|
||||
await expect(tokenController.verifyAccessToken(token)).rejects.toThrow(TokenExpiredError);
|
||||
});
|
||||
});
|
||||
|
||||
describe('openId state tokens', () => {
|
||||
test('issueOpenIdStateToken', async () => {
|
||||
const tokenController = createTokenController(context);
|
||||
const accessToken = await tokenController.issueOpenIdStateToken(openIdStateLocation);
|
||||
const claims = jwt.verify(accessToken, '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 tokenController = createTokenController(context);
|
||||
const accessToken = await tokenController.issueOpenIdStateToken({});
|
||||
const claims = jwt.verify(accessToken, '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,
|
||||
});
|
||||
});
|
||||
|
||||
test('verifyOpenIdStateToken', async () => {
|
||||
const tokenController = createTokenController(context);
|
||||
const accessToken = await tokenController.issueOpenIdStateToken(openIdStateLocation);
|
||||
const claims = await tokenController.verifyOpenIdStateToken(accessToken);
|
||||
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', async () => {
|
||||
const tokenController = createTokenController(context);
|
||||
const token = await tokenController.issueAccessToken(openIdClaims);
|
||||
await expect(tokenController.verifyOpenIdStateToken(token)).rejects.toThrow(
|
||||
AuthenticationError
|
||||
);
|
||||
});
|
||||
|
||||
test('verifyOpenIdStateToken invalid token', async () => {
|
||||
const tokenController = createTokenController(context);
|
||||
expect(tokenController.verifyOpenIdStateToken('not a token')).rejects.toThrow(
|
||||
AuthenticationError
|
||||
);
|
||||
});
|
||||
|
||||
test('verifyOpenIdStateToken token expired', async () => {
|
||||
const tokenController = createTokenController(context);
|
||||
const token = jwt.sign(
|
||||
{
|
||||
lowdefy_openid_state_token: true,
|
||||
exp: -10000,
|
||||
},
|
||||
'JWT_SECRET',
|
||||
{
|
||||
audience: 'host',
|
||||
issuer: 'host',
|
||||
}
|
||||
);
|
||||
await expect(tokenController.verifyOpenIdStateToken(token)).rejects.toThrow(
|
||||
AuthenticationError
|
||||
);
|
||||
});
|
||||
});
|
@ -14,24 +14,28 @@
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import createGetController from '../context/getController';
|
||||
import createGetController from '../controllers/getController';
|
||||
|
||||
function testBootstrapContext({ loaders, getSecrets } = {}) {
|
||||
function testBootstrapContext({ development, getSecrets, host, loaders } = {}) {
|
||||
const bootstrapContext = {
|
||||
CONFIGURATION_BASE_PATH: 'CONFIGURATION_BASE_PATH',
|
||||
getLoader: loaders ? (name) => loaders[name] : () => {},
|
||||
development,
|
||||
getController: () => {},
|
||||
getLoader: loaders ? (name) => loaders[name] : () => {},
|
||||
getSecrets: getSecrets || (() => {}),
|
||||
host: host || 'host',
|
||||
logger: { log: () => {} },
|
||||
};
|
||||
bootstrapContext.getController = createGetController(bootstrapContext);
|
||||
return bootstrapContext;
|
||||
}
|
||||
|
||||
function testContext({ loaders, getSecrets } = {}) {
|
||||
function testContext({ development, getSecrets, host, loaders } = {}) {
|
||||
const bootstrapContext = {
|
||||
development,
|
||||
getLoader: (name) => loaders[name],
|
||||
getSecrets: getSecrets || (() => {}),
|
||||
host: host || 'host',
|
||||
logger: { log: () => {} },
|
||||
};
|
||||
bootstrapContext.getController = createGetController(bootstrapContext);
|
||||
|
Loading…
Reference in New Issue
Block a user