|
|
|
@@ -74,6 +74,7 @@ describe('OAuthAdapter', () => {
|
|
|
|
|
providerId: 'test-provider',
|
|
|
|
|
appOrigin: 'http://localhost:3000',
|
|
|
|
|
baseUrl: 'http://domain.org/auth',
|
|
|
|
|
isPopupAuthenticationRequest: true,
|
|
|
|
|
cookieConfigurer: mockCookieConfigurer,
|
|
|
|
|
tokenIssuer: {
|
|
|
|
|
issueToken: async () => 'my-id-token',
|
|
|
|
@@ -83,56 +84,10 @@ describe('OAuthAdapter', () => {
|
|
|
|
|
callbackUrl: 'http://domain.org/auth/test-provider/handler/frame',
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
it('sets the correct headers in start', async () => {
|
|
|
|
|
const oauthProvider = new OAuthAdapter(
|
|
|
|
|
providerInstance,
|
|
|
|
|
oAuthProviderOptions,
|
|
|
|
|
);
|
|
|
|
|
const mockRequest = {
|
|
|
|
|
query: {
|
|
|
|
|
scope: 'user',
|
|
|
|
|
env: 'development',
|
|
|
|
|
},
|
|
|
|
|
} as unknown as express.Request;
|
|
|
|
|
const defaultState = { nonce: 'nonce', env: 'development' };
|
|
|
|
|
|
|
|
|
|
const mockResponse = {
|
|
|
|
|
cookie: jest.fn().mockReturnThis(),
|
|
|
|
|
end: jest.fn().mockReturnThis(),
|
|
|
|
|
setHeader: jest.fn().mockReturnThis(),
|
|
|
|
|
statusCode: jest.fn().mockReturnThis(),
|
|
|
|
|
} as unknown as express.Response;
|
|
|
|
|
|
|
|
|
|
await oauthProvider.start(mockRequest, mockResponse);
|
|
|
|
|
// nonce cookie checks
|
|
|
|
|
expect(mockResponse.cookie).toHaveBeenCalledTimes(1);
|
|
|
|
|
expect(mockResponse.cookie).toHaveBeenCalledWith(
|
|
|
|
|
`${oAuthProviderOptions.providerId}-nonce`,
|
|
|
|
|
expect.any(String),
|
|
|
|
|
expect.objectContaining({
|
|
|
|
|
httpOnly: true,
|
|
|
|
|
path: '/auth/test-provider/handler',
|
|
|
|
|
maxAge: TEN_MINUTES_MS,
|
|
|
|
|
domain: 'domain.org',
|
|
|
|
|
sameSite: 'lax',
|
|
|
|
|
secure: false,
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
// redirect checks
|
|
|
|
|
expect(mockResponse.setHeader).toHaveBeenCalledTimes(2);
|
|
|
|
|
expect(mockResponse.setHeader).toHaveBeenCalledWith('Location', '/url');
|
|
|
|
|
expect(mockResponse.setHeader).toHaveBeenCalledWith('Content-Length', '0');
|
|
|
|
|
expect(mockResponse.statusCode).toEqual(301);
|
|
|
|
|
expect(mockResponse.end).toHaveBeenCalledTimes(1);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('sets the refresh cookie if refresh is enabled', async () => {
|
|
|
|
|
const oauthProvider = new OAuthAdapter(providerInstance, {
|
|
|
|
|
...oAuthProviderOptions,
|
|
|
|
|
isOriginAllowed: () => false,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const state = { nonce: 'nonce', env: 'development' };
|
|
|
|
|
const mockRequest = {
|
|
|
|
|
const createEncodedQueryMockRequest = (state: any) => {
|
|
|
|
|
return {
|
|
|
|
|
cookies: {
|
|
|
|
|
'test-provider-nonce': 'nonce',
|
|
|
|
|
},
|
|
|
|
@@ -140,12 +95,69 @@ describe('OAuthAdapter', () => {
|
|
|
|
|
state: encodeState(state),
|
|
|
|
|
},
|
|
|
|
|
} as unknown as express.Request;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const mockResponse = {
|
|
|
|
|
cookie: jest.fn().mockReturnThis(),
|
|
|
|
|
setHeader: jest.fn().mockReturnThis(),
|
|
|
|
|
end: jest.fn().mockReturnThis(),
|
|
|
|
|
} as unknown as express.Response;
|
|
|
|
|
const mockResponse = {
|
|
|
|
|
cookie: jest.fn().mockReturnThis(),
|
|
|
|
|
end: jest.fn().mockReturnThis(),
|
|
|
|
|
setHeader: jest.fn().mockReturnThis(),
|
|
|
|
|
statusCode: jest.fn().mockReturnThis(),
|
|
|
|
|
redirect: jest.fn().mockReturnThis(),
|
|
|
|
|
status: jest.fn().mockReturnThis(),
|
|
|
|
|
json: jest.fn().mockReturnThis(),
|
|
|
|
|
} as unknown as express.Response;
|
|
|
|
|
|
|
|
|
|
const mockStartRequest = {
|
|
|
|
|
query: {
|
|
|
|
|
scope: 'user',
|
|
|
|
|
env: 'development',
|
|
|
|
|
},
|
|
|
|
|
} as unknown as express.Request;
|
|
|
|
|
|
|
|
|
|
const expectedStartAuthCookieData = {
|
|
|
|
|
httpOnly: true,
|
|
|
|
|
path: '/auth/test-provider/handler',
|
|
|
|
|
maxAge: TEN_MINUTES_MS,
|
|
|
|
|
domain: 'domain.org',
|
|
|
|
|
sameSite: 'lax',
|
|
|
|
|
secure: false,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
it('sets the correct headers in start', async () => {
|
|
|
|
|
const oauthProvider = new OAuthAdapter(
|
|
|
|
|
providerInstance,
|
|
|
|
|
oAuthProviderOptions,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
await oauthProvider.start(mockStartRequest, mockResponse);
|
|
|
|
|
// nonce cookie checks
|
|
|
|
|
expect(mockResponse.cookie).toHaveBeenCalledTimes(1);
|
|
|
|
|
expect(mockResponse.cookie).toHaveBeenCalledWith(
|
|
|
|
|
`${oAuthProviderOptions.providerId}-nonce`,
|
|
|
|
|
expect.any(String),
|
|
|
|
|
expect.objectContaining(expectedStartAuthCookieData),
|
|
|
|
|
);
|
|
|
|
|
expect(mockResponse.setHeader).toHaveBeenCalledTimes(2);
|
|
|
|
|
expect(mockResponse.setHeader).toHaveBeenCalledWith('Location', '/url');
|
|
|
|
|
expect(mockResponse.setHeader).toHaveBeenCalledWith('Content-Length', '0');
|
|
|
|
|
expect(mockResponse.statusCode).toEqual(301);
|
|
|
|
|
expect(mockResponse.end).toHaveBeenCalledTimes(1);
|
|
|
|
|
expect(mockResponse.redirect).not.toHaveBeenCalled();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const refreshCookieData = {
|
|
|
|
|
...expectedStartAuthCookieData,
|
|
|
|
|
path: '/auth/test-provider',
|
|
|
|
|
maxAge: THOUSAND_DAYS_MS,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
it('sets the refresh cookie if refresh is enabled', async () => {
|
|
|
|
|
const oauthProvider = new OAuthAdapter(providerInstance, {
|
|
|
|
|
...oAuthProviderOptions,
|
|
|
|
|
isOriginAllowed: () => false,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const mockRequest = createEncodedQueryMockRequest(defaultState);
|
|
|
|
|
|
|
|
|
|
await oauthProvider.frameHandler(mockRequest, mockResponse);
|
|
|
|
|
expect(mockCookieConfigurer).toHaveBeenCalledTimes(1);
|
|
|
|
@@ -153,15 +165,22 @@ describe('OAuthAdapter', () => {
|
|
|
|
|
expect(mockResponse.cookie).toHaveBeenCalledWith(
|
|
|
|
|
expect.stringContaining('test-provider-refresh-token'),
|
|
|
|
|
expect.stringContaining('token'),
|
|
|
|
|
expect.objectContaining({
|
|
|
|
|
httpOnly: true,
|
|
|
|
|
path: '/auth/test-provider',
|
|
|
|
|
maxAge: THOUSAND_DAYS_MS,
|
|
|
|
|
domain: 'domain.org',
|
|
|
|
|
secure: false,
|
|
|
|
|
sameSite: 'lax',
|
|
|
|
|
}),
|
|
|
|
|
expect.objectContaining(refreshCookieData),
|
|
|
|
|
);
|
|
|
|
|
expect(mockResponse.redirect).not.toHaveBeenCalled();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('sets the refresh cookie if refresh is enabled with redirect', async () => {
|
|
|
|
|
const oauthProvider = new OAuthAdapter(providerInstance, {
|
|
|
|
|
...oAuthProviderOptions,
|
|
|
|
|
isOriginAllowed: () => false,
|
|
|
|
|
isPopupAuthenticationRequest: false,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const mockRequest = createEncodedQueryMockRequest(defaultState);
|
|
|
|
|
|
|
|
|
|
await oauthProvider.frameHandler(mockRequest, mockResponse);
|
|
|
|
|
expect(mockResponse.redirect).toHaveBeenCalledTimes(1);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('persists scope through cookie if enabled', async () => {
|
|
|
|
@@ -179,32 +198,15 @@ describe('OAuthAdapter', () => {
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// First we test the /start request, making sure state is set
|
|
|
|
|
const mockStartReq = {
|
|
|
|
|
query: {
|
|
|
|
|
scope: 'user',
|
|
|
|
|
env: 'development',
|
|
|
|
|
},
|
|
|
|
|
} as unknown as express.Request;
|
|
|
|
|
const mockStartRes = {
|
|
|
|
|
cookie: jest.fn().mockReturnThis(),
|
|
|
|
|
end: jest.fn().mockReturnThis(),
|
|
|
|
|
setHeader: jest.fn().mockReturnThis(),
|
|
|
|
|
statusCode: jest.fn().mockReturnThis(),
|
|
|
|
|
} as unknown as express.Response;
|
|
|
|
|
|
|
|
|
|
await oauthProvider.start(mockStartReq, mockStartRes);
|
|
|
|
|
await oauthProvider.start(mockStartRequest, mockResponse);
|
|
|
|
|
|
|
|
|
|
expect(handlers.start).toHaveBeenCalledTimes(1);
|
|
|
|
|
expect(handlers.start).toHaveBeenCalledWith({
|
|
|
|
|
query: {
|
|
|
|
|
scope: 'user',
|
|
|
|
|
env: 'development',
|
|
|
|
|
},
|
|
|
|
|
...mockStartRequest,
|
|
|
|
|
scope: 'user',
|
|
|
|
|
state: {
|
|
|
|
|
nonce: expect.any(String),
|
|
|
|
|
env: 'development',
|
|
|
|
|
origin: undefined,
|
|
|
|
|
scope: 'user',
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
@@ -223,6 +225,7 @@ describe('OAuthAdapter', () => {
|
|
|
|
|
cookie: jest.fn().mockReturnThis(),
|
|
|
|
|
setHeader: jest.fn().mockReturnThis(),
|
|
|
|
|
end: jest.fn().mockReturnThis(),
|
|
|
|
|
redirect: jest.fn().mockReturnThis(),
|
|
|
|
|
} as unknown as express.Response;
|
|
|
|
|
|
|
|
|
|
await oauthProvider.frameHandler(mockHandleReq, mockHandleRes);
|
|
|
|
@@ -230,17 +233,11 @@ describe('OAuthAdapter', () => {
|
|
|
|
|
expect(mockHandleRes.cookie).toHaveBeenCalledWith(
|
|
|
|
|
'test-provider-granted-scope',
|
|
|
|
|
'user',
|
|
|
|
|
expect.objectContaining({
|
|
|
|
|
httpOnly: true,
|
|
|
|
|
path: '/auth/test-provider',
|
|
|
|
|
maxAge: THOUSAND_DAYS_MS,
|
|
|
|
|
domain: 'domain.org',
|
|
|
|
|
secure: false,
|
|
|
|
|
sameSite: 'lax',
|
|
|
|
|
}),
|
|
|
|
|
expect.objectContaining(refreshCookieData),
|
|
|
|
|
);
|
|
|
|
|
expect(mockResponse.redirect).not.toHaveBeenCalled();
|
|
|
|
|
|
|
|
|
|
// Them make sure scopes are forwarded correctly during refresh
|
|
|
|
|
// Then make sure scopes are forwarded correctly during refresh
|
|
|
|
|
const mockRefreshReq = {
|
|
|
|
|
query: { scope: 'ignore-me' },
|
|
|
|
|
cookies: {
|
|
|
|
@@ -252,6 +249,7 @@ describe('OAuthAdapter', () => {
|
|
|
|
|
const mockRefreshRes = {
|
|
|
|
|
status: jest.fn().mockReturnThis(),
|
|
|
|
|
json: jest.fn().mockReturnThis(),
|
|
|
|
|
redirect: jest.fn().mockReturnThis(),
|
|
|
|
|
} as unknown as express.Response;
|
|
|
|
|
await oauthProvider.refresh(mockRefreshReq, mockRefreshRes);
|
|
|
|
|
expect(handlers.refresh).toHaveBeenCalledTimes(1);
|
|
|
|
@@ -261,8 +259,18 @@ describe('OAuthAdapter', () => {
|
|
|
|
|
refreshToken: 'refresh-token',
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
expect(mockRefreshRes.redirect).not.toHaveBeenCalled();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const mockRequestWithHeader = {
|
|
|
|
|
header: () => 'XMLHttpRequest',
|
|
|
|
|
cookies: {
|
|
|
|
|
'test-provider-refresh-token': 'token',
|
|
|
|
|
},
|
|
|
|
|
query: {},
|
|
|
|
|
get: jest.fn(),
|
|
|
|
|
} as unknown as express.Request;
|
|
|
|
|
|
|
|
|
|
it('removes refresh cookie and calls logout handler when logging out', async () => {
|
|
|
|
|
const logoutSpy = jest.spyOn(providerInstance, 'logout');
|
|
|
|
|
const oauthProvider = new OAuthAdapter(providerInstance, {
|
|
|
|
@@ -270,22 +278,8 @@ describe('OAuthAdapter', () => {
|
|
|
|
|
isOriginAllowed: () => false,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const mockRequest = {
|
|
|
|
|
cookies: {
|
|
|
|
|
'test-provider-refresh-token': 'token',
|
|
|
|
|
},
|
|
|
|
|
header: () => 'XMLHttpRequest',
|
|
|
|
|
get: jest.fn(),
|
|
|
|
|
} as unknown as express.Request;
|
|
|
|
|
|
|
|
|
|
const mockResponse = {
|
|
|
|
|
cookie: jest.fn().mockReturnThis(),
|
|
|
|
|
end: jest.fn().mockReturnThis(),
|
|
|
|
|
status: jest.fn().mockReturnThis(),
|
|
|
|
|
} as unknown as express.Response;
|
|
|
|
|
|
|
|
|
|
await oauthProvider.logout(mockRequest, mockResponse);
|
|
|
|
|
expect(mockRequest.get).toHaveBeenCalledTimes(1);
|
|
|
|
|
await oauthProvider.logout(mockRequestWithHeader, mockResponse);
|
|
|
|
|
expect(mockRequestWithHeader.get).toHaveBeenCalledTimes(1);
|
|
|
|
|
expect(logoutSpy).toHaveBeenCalledTimes(1);
|
|
|
|
|
expect(mockResponse.cookie).toHaveBeenCalledTimes(1);
|
|
|
|
|
expect(mockResponse.cookie).toHaveBeenCalledWith(
|
|
|
|
@@ -294,6 +288,7 @@ describe('OAuthAdapter', () => {
|
|
|
|
|
expect.objectContaining({ path: '/auth/test-provider' }),
|
|
|
|
|
);
|
|
|
|
|
expect(mockResponse.end).toHaveBeenCalledTimes(1);
|
|
|
|
|
expect(mockResponse.redirect).not.toHaveBeenCalled();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('gets new access-token when refreshing', async () => {
|
|
|
|
@@ -302,20 +297,7 @@ describe('OAuthAdapter', () => {
|
|
|
|
|
isOriginAllowed: () => false,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const mockRequest = {
|
|
|
|
|
header: () => 'XMLHttpRequest',
|
|
|
|
|
cookies: {
|
|
|
|
|
'test-provider-refresh-token': 'token',
|
|
|
|
|
},
|
|
|
|
|
query: {},
|
|
|
|
|
} as unknown as express.Request;
|
|
|
|
|
|
|
|
|
|
const mockResponse = {
|
|
|
|
|
json: jest.fn().mockReturnThis(),
|
|
|
|
|
status: jest.fn().mockReturnThis(),
|
|
|
|
|
} as unknown as express.Response;
|
|
|
|
|
|
|
|
|
|
await oauthProvider.refresh(mockRequest, mockResponse);
|
|
|
|
|
await oauthProvider.refresh(mockRequestWithHeader, mockResponse);
|
|
|
|
|
expect(mockResponse.json).toHaveBeenCalledTimes(1);
|
|
|
|
|
expect(mockResponse.json).toHaveBeenCalledWith({
|
|
|
|
|
...mockResponseData,
|
|
|
|
@@ -328,6 +310,7 @@ describe('OAuthAdapter', () => {
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
expect(mockResponse.redirect).not.toHaveBeenCalled();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('sets new access-token when old cookie exists', async () => {
|
|
|
|
@@ -337,20 +320,12 @@ describe('OAuthAdapter', () => {
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const mockRequest = {
|
|
|
|
|
header: () => 'XMLHttpRequest',
|
|
|
|
|
...mockRequestWithHeader,
|
|
|
|
|
cookies: {
|
|
|
|
|
'test-provider-refresh-token': 'old-token',
|
|
|
|
|
},
|
|
|
|
|
query: {},
|
|
|
|
|
get: jest.fn(),
|
|
|
|
|
} as unknown as express.Request;
|
|
|
|
|
|
|
|
|
|
const mockResponse = {
|
|
|
|
|
json: jest.fn().mockReturnThis(),
|
|
|
|
|
status: jest.fn().mockReturnThis(),
|
|
|
|
|
cookie: jest.fn().mockReturnThis(),
|
|
|
|
|
} as unknown as express.Response;
|
|
|
|
|
|
|
|
|
|
await oauthProvider.refresh(mockRequest, mockResponse);
|
|
|
|
|
expect(mockRequest.get).toHaveBeenCalledTimes(1);
|
|
|
|
|
expect(mockCookieConfigurer).toHaveBeenCalledTimes(1);
|
|
|
|
@@ -358,15 +333,9 @@ describe('OAuthAdapter', () => {
|
|
|
|
|
expect(mockResponse.cookie).toHaveBeenCalledWith(
|
|
|
|
|
'test-provider-refresh-token',
|
|
|
|
|
'token',
|
|
|
|
|
expect.objectContaining({
|
|
|
|
|
httpOnly: true,
|
|
|
|
|
path: '/auth/test-provider',
|
|
|
|
|
maxAge: THOUSAND_DAYS_MS,
|
|
|
|
|
domain: 'domain.org',
|
|
|
|
|
secure: false,
|
|
|
|
|
sameSite: 'lax',
|
|
|
|
|
}),
|
|
|
|
|
expect.objectContaining(refreshCookieData),
|
|
|
|
|
);
|
|
|
|
|
expect(mockResponse.redirect).not.toHaveBeenCalled();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('sets the correct nonce cookie configuration', async () => {
|
|
|
|
@@ -374,168 +343,97 @@ describe('OAuthAdapter', () => {
|
|
|
|
|
baseUrl: 'http://domain.org/auth',
|
|
|
|
|
appUrl: 'http://domain.org',
|
|
|
|
|
isOriginAllowed: () => false,
|
|
|
|
|
isPopupAuthenticationRequest: true,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const oauthProvider = OAuthAdapter.fromConfig(config, providerInstance, {
|
|
|
|
|
...oAuthProviderOptions,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const mockRequest = {
|
|
|
|
|
query: {
|
|
|
|
|
scope: 'user',
|
|
|
|
|
env: 'development',
|
|
|
|
|
origin: 'http://domain.org',
|
|
|
|
|
},
|
|
|
|
|
} as unknown as express.Request;
|
|
|
|
|
|
|
|
|
|
const mockResponse = {
|
|
|
|
|
cookie: jest.fn().mockReturnThis(),
|
|
|
|
|
end: jest.fn().mockReturnThis(),
|
|
|
|
|
setHeader: jest.fn().mockReturnThis(),
|
|
|
|
|
statusCode: jest.fn().mockReturnThis(),
|
|
|
|
|
} as unknown as express.Response;
|
|
|
|
|
|
|
|
|
|
await oauthProvider.start(mockRequest, mockResponse);
|
|
|
|
|
await oauthProvider.start(mockStartRequest, mockResponse);
|
|
|
|
|
expect(mockCookieConfigurer).not.toHaveBeenCalled();
|
|
|
|
|
expect(mockResponse.cookie).toHaveBeenCalledTimes(1);
|
|
|
|
|
expect(mockResponse.cookie).toHaveBeenCalledWith(
|
|
|
|
|
`${oAuthProviderOptions.providerId}-nonce`,
|
|
|
|
|
expect.any(String),
|
|
|
|
|
expect.objectContaining({
|
|
|
|
|
httpOnly: true,
|
|
|
|
|
domain: 'domain.org',
|
|
|
|
|
maxAge: TEN_MINUTES_MS,
|
|
|
|
|
path: '/auth/test-provider/handler',
|
|
|
|
|
secure: false,
|
|
|
|
|
sameSite: 'lax',
|
|
|
|
|
}),
|
|
|
|
|
expect.objectContaining(expectedStartAuthCookieData),
|
|
|
|
|
);
|
|
|
|
|
expect(mockResponse.redirect).not.toHaveBeenCalled();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('sets the correct nonce cookie configuration using origin from request', async () => {
|
|
|
|
|
const config = {
|
|
|
|
|
baseUrl: 'http://domain.org/auth',
|
|
|
|
|
appUrl: 'http://domain.org',
|
|
|
|
|
isOriginAllowed: () => false,
|
|
|
|
|
};
|
|
|
|
|
const config = {
|
|
|
|
|
baseUrl: 'http://domain.org/auth',
|
|
|
|
|
appUrl: 'http://domain.org',
|
|
|
|
|
isOriginAllowed: () => false,
|
|
|
|
|
isPopupAuthenticationRequest: true,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const mockStartRequestWithOrigin = {
|
|
|
|
|
query: {
|
|
|
|
|
scope: 'user',
|
|
|
|
|
env: 'development',
|
|
|
|
|
origin: 'http://other.domain',
|
|
|
|
|
},
|
|
|
|
|
} as unknown as express.Request;
|
|
|
|
|
|
|
|
|
|
it('sets the correct nonce cookie configuration using origin from request', async () => {
|
|
|
|
|
const oauthProvider = OAuthAdapter.fromConfig(config, providerInstance, {
|
|
|
|
|
...oAuthProviderOptions,
|
|
|
|
|
callbackUrl: 'https://domain.org/auth/test-provider/handler/frame',
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const mockRequest = {
|
|
|
|
|
query: {
|
|
|
|
|
scope: 'user',
|
|
|
|
|
env: 'development',
|
|
|
|
|
origin: 'http://other.domain',
|
|
|
|
|
},
|
|
|
|
|
} as unknown as express.Request;
|
|
|
|
|
|
|
|
|
|
const mockResponse = {
|
|
|
|
|
cookie: jest.fn().mockReturnThis(),
|
|
|
|
|
end: jest.fn().mockReturnThis(),
|
|
|
|
|
setHeader: jest.fn().mockReturnThis(),
|
|
|
|
|
statusCode: jest.fn().mockReturnThis(),
|
|
|
|
|
} as unknown as express.Response;
|
|
|
|
|
|
|
|
|
|
await oauthProvider.start(mockRequest, mockResponse);
|
|
|
|
|
await oauthProvider.start(mockStartRequestWithOrigin, mockResponse);
|
|
|
|
|
expect(mockCookieConfigurer).not.toHaveBeenCalled();
|
|
|
|
|
expect(mockResponse.cookie).toHaveBeenCalledTimes(1);
|
|
|
|
|
expect(mockResponse.cookie).toHaveBeenCalledWith(
|
|
|
|
|
`${oAuthProviderOptions.providerId}-nonce`,
|
|
|
|
|
expect.any(String),
|
|
|
|
|
expect.objectContaining({
|
|
|
|
|
httpOnly: true,
|
|
|
|
|
domain: 'domain.org',
|
|
|
|
|
maxAge: TEN_MINUTES_MS,
|
|
|
|
|
path: '/auth/test-provider/handler',
|
|
|
|
|
...expectedStartAuthCookieData,
|
|
|
|
|
secure: true,
|
|
|
|
|
sameSite: 'none',
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
expect(mockResponse.redirect).not.toHaveBeenCalled();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('sets the correct cookie configuration using an secure callbackUrl', async () => {
|
|
|
|
|
const config = {
|
|
|
|
|
baseUrl: 'https://domain.org/auth',
|
|
|
|
|
appUrl: 'http://domain.org',
|
|
|
|
|
isOriginAllowed: () => false,
|
|
|
|
|
};
|
|
|
|
|
const secureCookieData = {
|
|
|
|
|
...refreshCookieData,
|
|
|
|
|
secure: true,
|
|
|
|
|
sameSite: 'lax',
|
|
|
|
|
maxAge: THOUSAND_DAYS_MS,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
it('sets the correct cookie configuration using an secure callbackUrl', async () => {
|
|
|
|
|
const oauthProvider = OAuthAdapter.fromConfig(config, providerInstance, {
|
|
|
|
|
...oAuthProviderOptions,
|
|
|
|
|
callbackUrl: 'https://domain.org/auth/test-provider/handler/frame',
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const state = {
|
|
|
|
|
nonce: 'nonce',
|
|
|
|
|
env: 'development',
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const mockRequest = {
|
|
|
|
|
cookies: {
|
|
|
|
|
'test-provider-nonce': 'nonce',
|
|
|
|
|
},
|
|
|
|
|
query: {
|
|
|
|
|
state: encodeState(state),
|
|
|
|
|
},
|
|
|
|
|
} as unknown as express.Request;
|
|
|
|
|
|
|
|
|
|
const mockResponse = {
|
|
|
|
|
cookie: jest.fn().mockReturnThis(),
|
|
|
|
|
setHeader: jest.fn().mockReturnThis(),
|
|
|
|
|
end: jest.fn().mockReturnThis(),
|
|
|
|
|
} as unknown as express.Response;
|
|
|
|
|
|
|
|
|
|
const mockRequest = createEncodedQueryMockRequest(defaultState);
|
|
|
|
|
await oauthProvider.frameHandler(mockRequest, mockResponse);
|
|
|
|
|
expect(mockCookieConfigurer).not.toHaveBeenCalled();
|
|
|
|
|
expect(mockResponse.cookie).toHaveBeenCalledTimes(1);
|
|
|
|
|
expect(mockResponse.cookie).toHaveBeenCalledWith(
|
|
|
|
|
expect.stringContaining('test-provider-refresh-token'),
|
|
|
|
|
expect.stringContaining('token'),
|
|
|
|
|
expect.objectContaining({
|
|
|
|
|
httpOnly: true,
|
|
|
|
|
domain: 'domain.org',
|
|
|
|
|
path: '/auth/test-provider',
|
|
|
|
|
secure: true,
|
|
|
|
|
sameSite: 'lax',
|
|
|
|
|
}),
|
|
|
|
|
expect.objectContaining(secureCookieData),
|
|
|
|
|
);
|
|
|
|
|
expect(mockResponse.redirect).not.toHaveBeenCalled();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('sets the correct cookie configuration when on different domains and secure', async () => {
|
|
|
|
|
const config = {
|
|
|
|
|
baseUrl: 'https://domain.org/auth',
|
|
|
|
|
appUrl: 'http://domain.org',
|
|
|
|
|
isOriginAllowed: () => false,
|
|
|
|
|
};
|
|
|
|
|
const secureSameSiteNoneCookieData = {
|
|
|
|
|
...secureCookieData,
|
|
|
|
|
sameSite: 'none',
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
it('sets the correct cookie configuration when on different domains and secure', async () => {
|
|
|
|
|
const oauthProvider = OAuthAdapter.fromConfig(config, providerInstance, {
|
|
|
|
|
...oAuthProviderOptions,
|
|
|
|
|
callbackUrl: 'https://authdomain.org/auth/test-provider/handler/frame',
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const state = {
|
|
|
|
|
nonce: 'nonce',
|
|
|
|
|
env: 'development',
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const mockRequest = {
|
|
|
|
|
cookies: {
|
|
|
|
|
'test-provider-nonce': 'nonce',
|
|
|
|
|
},
|
|
|
|
|
query: {
|
|
|
|
|
state: encodeState(state),
|
|
|
|
|
},
|
|
|
|
|
} as unknown as express.Request;
|
|
|
|
|
|
|
|
|
|
const mockResponse = {
|
|
|
|
|
cookie: jest.fn().mockReturnThis(),
|
|
|
|
|
setHeader: jest.fn().mockReturnThis(),
|
|
|
|
|
end: jest.fn().mockReturnThis(),
|
|
|
|
|
} as unknown as express.Response;
|
|
|
|
|
|
|
|
|
|
const mockRequest = createEncodedQueryMockRequest(defaultState);
|
|
|
|
|
await oauthProvider.frameHandler(mockRequest, mockResponse);
|
|
|
|
|
expect(mockCookieConfigurer).not.toHaveBeenCalled();
|
|
|
|
|
expect(mockResponse.cookie).toHaveBeenCalledTimes(1);
|
|
|
|
@@ -543,106 +441,109 @@ describe('OAuthAdapter', () => {
|
|
|
|
|
expect.stringContaining('test-provider-refresh-token'),
|
|
|
|
|
expect.stringContaining('token'),
|
|
|
|
|
expect.objectContaining({
|
|
|
|
|
httpOnly: true,
|
|
|
|
|
...secureSameSiteNoneCookieData,
|
|
|
|
|
domain: 'authdomain.org',
|
|
|
|
|
path: '/auth/test-provider',
|
|
|
|
|
secure: true,
|
|
|
|
|
sameSite: 'none',
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
expect(mockResponse.redirect).not.toHaveBeenCalled();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const configOriginAllowed = {
|
|
|
|
|
...config,
|
|
|
|
|
isOriginAllowed: () => true,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
it('sets the correct cookie configuration using origin from state', async () => {
|
|
|
|
|
const config = {
|
|
|
|
|
baseUrl: 'https://domain.org/auth',
|
|
|
|
|
appUrl: 'http://domain.org',
|
|
|
|
|
isOriginAllowed: () => true,
|
|
|
|
|
};
|
|
|
|
|
const oauthProvider = OAuthAdapter.fromConfig(
|
|
|
|
|
configOriginAllowed,
|
|
|
|
|
providerInstance,
|
|
|
|
|
{
|
|
|
|
|
...oAuthProviderOptions,
|
|
|
|
|
callbackUrl: 'https://domain.org/auth/test-provider/handler/frame',
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const oauthProvider = OAuthAdapter.fromConfig(config, providerInstance, {
|
|
|
|
|
...oAuthProviderOptions,
|
|
|
|
|
callbackUrl: 'https://domain.org/auth/test-provider/handler/frame',
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const state = {
|
|
|
|
|
nonce: 'nonce',
|
|
|
|
|
env: 'development',
|
|
|
|
|
const mockRequest = createEncodedQueryMockRequest({
|
|
|
|
|
...defaultState,
|
|
|
|
|
origin: 'http://other.domain',
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const mockRequest = {
|
|
|
|
|
cookies: {
|
|
|
|
|
'test-provider-nonce': 'nonce',
|
|
|
|
|
},
|
|
|
|
|
query: {
|
|
|
|
|
state: encodeState(state),
|
|
|
|
|
},
|
|
|
|
|
} as unknown as express.Request;
|
|
|
|
|
|
|
|
|
|
const mockResponse = {
|
|
|
|
|
cookie: jest.fn().mockReturnThis(),
|
|
|
|
|
setHeader: jest.fn().mockReturnThis(),
|
|
|
|
|
end: jest.fn().mockReturnThis(),
|
|
|
|
|
} as unknown as express.Response;
|
|
|
|
|
|
|
|
|
|
});
|
|
|
|
|
await oauthProvider.frameHandler(mockRequest, mockResponse);
|
|
|
|
|
expect(mockCookieConfigurer).not.toHaveBeenCalled();
|
|
|
|
|
expect(mockResponse.cookie).toHaveBeenCalledTimes(1);
|
|
|
|
|
expect(mockResponse.cookie).toHaveBeenCalledWith(
|
|
|
|
|
expect.stringContaining('test-provider-refresh-token'),
|
|
|
|
|
expect.stringContaining('token'),
|
|
|
|
|
expect.objectContaining({
|
|
|
|
|
httpOnly: true,
|
|
|
|
|
domain: 'domain.org',
|
|
|
|
|
path: '/auth/test-provider',
|
|
|
|
|
secure: true,
|
|
|
|
|
sameSite: 'none',
|
|
|
|
|
}),
|
|
|
|
|
expect.objectContaining(secureSameSiteNoneCookieData),
|
|
|
|
|
);
|
|
|
|
|
expect(mockResponse.redirect).not.toHaveBeenCalled();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('sets the correct cookie configuration using origin from header', async () => {
|
|
|
|
|
const config = {
|
|
|
|
|
baseUrl: 'https://domain.org/auth',
|
|
|
|
|
appUrl: 'http://domain.org',
|
|
|
|
|
isOriginAllowed: () => false,
|
|
|
|
|
};
|
|
|
|
|
const mockRequestWithGetMockReturn = {
|
|
|
|
|
header: () => 'XMLHttpRequest',
|
|
|
|
|
cookies: {
|
|
|
|
|
'test-provider-refresh-token': 'old-token',
|
|
|
|
|
},
|
|
|
|
|
query: {},
|
|
|
|
|
get: jest.fn().mockReturnValue('http://other.domain'),
|
|
|
|
|
} as unknown as express.Request;
|
|
|
|
|
|
|
|
|
|
it('sets the correct cookie configuration using origin from header', async () => {
|
|
|
|
|
const oauthProvider = OAuthAdapter.fromConfig(config, providerInstance, {
|
|
|
|
|
...oAuthProviderOptions,
|
|
|
|
|
callbackUrl: 'https://domain.org/auth/test-provider/handler/frame',
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const mockRequest = {
|
|
|
|
|
header: () => 'XMLHttpRequest',
|
|
|
|
|
cookies: {
|
|
|
|
|
'test-provider-refresh-token': 'old-token',
|
|
|
|
|
},
|
|
|
|
|
query: {},
|
|
|
|
|
get: jest.fn().mockReturnValue('http://other.domain'),
|
|
|
|
|
} as unknown as express.Request;
|
|
|
|
|
|
|
|
|
|
const mockResponse = {
|
|
|
|
|
json: jest.fn().mockReturnThis(),
|
|
|
|
|
status: jest.fn().mockReturnThis(),
|
|
|
|
|
cookie: jest.fn().mockReturnThis(),
|
|
|
|
|
} as unknown as express.Response;
|
|
|
|
|
|
|
|
|
|
await oauthProvider.refresh(mockRequest, mockResponse);
|
|
|
|
|
expect(mockRequest.get).toHaveBeenCalledTimes(1);
|
|
|
|
|
await oauthProvider.refresh(mockRequestWithGetMockReturn, mockResponse);
|
|
|
|
|
expect(mockRequestWithGetMockReturn.get).toHaveBeenCalledTimes(1);
|
|
|
|
|
expect(mockCookieConfigurer).not.toHaveBeenCalled();
|
|
|
|
|
expect(mockResponse.cookie).toHaveBeenCalledTimes(1);
|
|
|
|
|
expect(mockResponse.cookie).toHaveBeenCalledWith(
|
|
|
|
|
'test-provider-refresh-token',
|
|
|
|
|
'token',
|
|
|
|
|
expect.objectContaining({
|
|
|
|
|
httpOnly: true,
|
|
|
|
|
path: '/auth/test-provider',
|
|
|
|
|
maxAge: THOUSAND_DAYS_MS,
|
|
|
|
|
domain: 'domain.org',
|
|
|
|
|
secure: true,
|
|
|
|
|
sameSite: 'none',
|
|
|
|
|
}),
|
|
|
|
|
expect.objectContaining(secureSameSiteNoneCookieData),
|
|
|
|
|
);
|
|
|
|
|
expect(mockResponse.redirect).not.toHaveBeenCalled();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('executed a response redirect when isPopupAuthenticationRequest is false', async () => {
|
|
|
|
|
const handlers = {
|
|
|
|
|
start: jest.fn(async (_req: { state: OAuthState }) => ({
|
|
|
|
|
url: '/url',
|
|
|
|
|
status: 301,
|
|
|
|
|
})),
|
|
|
|
|
handler: jest.fn(async () => ({ response: mockResponseData })),
|
|
|
|
|
refresh: jest.fn(async () => ({ response: mockResponseData })),
|
|
|
|
|
};
|
|
|
|
|
const configWithNoPopupEnabled = {
|
|
|
|
|
...configOriginAllowed,
|
|
|
|
|
isPopupAuthenticationRequest: false,
|
|
|
|
|
};
|
|
|
|
|
const oauthProvider = OAuthAdapter.fromConfig(
|
|
|
|
|
configWithNoPopupEnabled,
|
|
|
|
|
handlers,
|
|
|
|
|
{
|
|
|
|
|
...oAuthProviderOptions,
|
|
|
|
|
callbackUrl: 'https://domain.org/auth/test-provider/handler/frame',
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const state = {
|
|
|
|
|
...defaultState,
|
|
|
|
|
origin: 'http://other.domain',
|
|
|
|
|
redirectUrl: 'http://domain.org',
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const mockRequest = {
|
|
|
|
|
...createEncodedQueryMockRequest(state),
|
|
|
|
|
get: jest.fn().mockReturnValue('http://other.domain'),
|
|
|
|
|
} as unknown as express.Request;
|
|
|
|
|
|
|
|
|
|
await oauthProvider.frameHandler(mockRequest, mockResponse);
|
|
|
|
|
expect(mockRequest.get).not.toHaveBeenCalled();
|
|
|
|
|
expect(mockResponse.end).not.toHaveBeenCalled();
|
|
|
|
|
expect(mockCookieConfigurer).not.toHaveBeenCalled();
|
|
|
|
|
expect(mockResponse.cookie).not.toHaveBeenCalled();
|
|
|
|
|
expect(mockResponse.redirect).toHaveBeenCalledTimes(1);
|
|
|
|
|
expect(mockResponse.redirect).toHaveBeenCalledWith('http://domain.org');
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|