Skip to content

Commit

Permalink
feat: add redirectTo login webhook response property support, improve…
Browse files Browse the repository at this point in the history
… tests
  • Loading branch information
vlad-tkachenko committed Nov 20, 2023
1 parent 104c505 commit b7b5c63
Show file tree
Hide file tree
Showing 6 changed files with 167 additions and 11 deletions.
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,10 +136,11 @@ Example:

#### Webhooks
- `WEBHOOK_LOGIN_URL` - [optional]optional URL to make a POST request to, response should be a json object with the following optional fields
- `refresh: boolean` - [optional] if true, service will use refresh token to fetch new set of tokens, might be useful when webhook endpoint updated user state and new set of tokens should be issued to a user
- `reject: boolean` - [optional] if true, user won't get the tokens and will see an `Access denied` error
- `reason: string` - [optional] reason to return instead of `Access denied`
- `meta: Record<string, any>` - [optional] custom meta attributes associated to a user (make sure to use `JWT_META_TOKEN_SECRET` env variable to set secret and `HEADERS_META` to set the header name to proxy value in)
- `refresh: boolean` - [optional] if true, service will use refresh token to fetch new set of tokens, might be useful when webhook endpoint updated user state and new set of tokens should be issued to a user
- `reject: boolean` - [optional] if true, user won't get the tokens and will see an `Access denied` error
- `reason: string` - [optional] reason to return instead of `Access denied`
- `meta: Record<string, any>` - [optional] custom meta attributes associated to a user (make sure to use `JWT_META_TOKEN_SECRET` env variable to set secret and `HEADERS_META` to set the header name to proxy value in)
- `redirectTo: string` - [optional] custom URL or relative path to redirect upon flow completion

## Links

Expand Down
19 changes: 18 additions & 1 deletion src/handlers/CallbackHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export const CallbackHandler: RequestHandlerConfig = {

const cookies = RequestUtils.getCookies(req);
const originalPath = cookies[getConfig().cookies.names.originalPath] || '/';
let redirectTo = `${getConfig().hostURL}${originalPath}`;

// login webhook handler (if any)
if (getConfig().webhook.login) {
Expand Down Expand Up @@ -63,9 +64,25 @@ export const CallbackHandler: RequestHandlerConfig = {
logger.child({meta: result.meta}).debug('Webhook returned custom user attributes');
metaToken = OpenIDUtils.prepareMetaToken(result.meta);
}

if (result.redirectTo) {
logger.child({redirectTo: result}).debug('Webhook returned custom redirect endpoint');
redirectTo = result.redirectTo;

// if relative path
/* istanbul ignore else */
if (redirectTo.indexOf('http') < 0) {
// append slash if missing
/* istanbul ignore else */
if (redirectTo.indexOf('/') !== 0) {
redirectTo = '/' + redirectTo;
}
redirectTo = `${getConfig().hostURL}${redirectTo}`;
}
}
}

setAuthCookies(res, tokens, metaToken);
await sendRedirect(res, `${getConfig().hostURL}${originalPath}`);
await sendRedirect(res, redirectTo);
}
}
2 changes: 1 addition & 1 deletion src/utils/ResponseUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ const setCookies = (resp: ServerResponse, cookies: Record<string, {value: string
* @param url
*/
export const sendRedirect = async (resp: ServerResponse, url: string): Promise<void> => {
getLogger('ResponseUtils').debug('Sending redirect');
getLogger('ResponseUtils').child({ url }).debug('Sending redirect');
resp.statusCode = 307;
resp.setHeader('Location', url);
resp.end();
Expand Down
17 changes: 17 additions & 0 deletions test/PageMapping.suite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,23 @@ class PageMappingSuite extends BaseSuite {
});
}

@test()
async e404EndpointWithoutRedirect() {
await this.reloadPrxiWith({
redirect: {
pageRequest: {
e404: null,
}
}
});

const uri = '/non-existing-mapping';
await this.withNewPage(getConfig().hostURL + uri, async (page) => {
const text = await this.getTextFromPage(page);
strictEqual(text, '404: Not found')
});
}

@test()
async e403Endpoint() {
const uri = '/forbidden-pages/test?q=str';
Expand Down
81 changes: 80 additions & 1 deletion test/WebhookHandler.suite.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { suite, test } from "@testdeck/mocha";
import { BaseSuite } from "./Base.suite";
import { getConfig } from "../src/config/getConfig";
import { ok, strictEqual } from "assert";
import { strictEqual } from "assert";

const OpenApiMocker = require('open-api-mocker');

Expand All @@ -11,6 +11,9 @@ class PublicMappingSuite extends BaseSuite {

private static mockPort = 7777;
private static rejectURL = `http://localhost:${PublicMappingSuite.mockPort}/reject`;
private static loginFailure = `http://localhost:${PublicMappingSuite.mockPort}/login-fail`;
private static redirectToURL = `http://localhost:${PublicMappingSuite.mockPort}/redirectTo`;
private static refreshToken = `http://localhost:${PublicMappingSuite.mockPort}/refreshToken`;
private static logout = `http://localhost:${PublicMappingSuite.mockPort}/logout`;
private static logoutFailure = `http://localhost:${PublicMappingSuite.mockPort}/logout-fail`;
private static metaURL = `http://localhost:${PublicMappingSuite.mockPort}/meta`;
Expand Down Expand Up @@ -60,6 +63,82 @@ class PublicMappingSuite extends BaseSuite {
});
}

@test()
async rejectLoginWithoutRedirectConfig(): Promise<void> {
await this.reloadPrxiWith({
webhook: {
login: PublicMappingSuite.rejectURL,
},
redirect: {
pageRequest: {
e403: null,
}
}
});

await this.withNewPage(getConfig().hostURL + '/pages/test', async (page) => {
await this.loginOnKeycloak(page);
const text = await this.getTextFromPage(page);
strictEqual(text, '403: Forbidden')
});
}

@test()
async refreshToken(): Promise<void> {
await this.reloadPrxiWith({
webhook: {
login: PublicMappingSuite.refreshToken,
}
});

const uri = '/pages/test';
await this.withNewPage(getConfig().hostURL + uri, async (page) => {
await this.loginOnKeycloak(page);

// navigate to the same page again
await this.navigate(page, getConfig().hostURL + uri);
const text = await this.getTextFromPage(page);
const json = JSON.parse(text);
strictEqual(json.http.originalUrl, uri);
});
}

@test()
async testRedirectTo(): Promise<void> {
const uri = '/api/test?q=str';

await this.reloadPrxiWith({
webhook: {
login: PublicMappingSuite.redirectToURL,
}
});

await this.withNewPage(getConfig().hostURL + '/pages/test', async (page) => {
await this.loginOnKeycloak(page);
const json = await this.getJsonFromPage(page);

// validate query to be in place
strictEqual(json.http.originalUrl, uri);
strictEqual(json.request.query.q, 'str');
});
}

@test()
async testLoginFailure(): Promise<void> {
await this.reloadPrxiWith({
webhook: {
login: PublicMappingSuite.loginFailure,
}
});

await this.withNewPage(getConfig().hostURL + '/pages/test', async (page) => {
await this.loginOnKeycloak(page);

const text = await this.getTextFromPage(page);
strictEqual(text, '500: Unexpected error occurred')
});
}

@test()
async testMeta(): Promise<void> {
await this.reloadPrxiWith({
Expand Down
50 changes: 46 additions & 4 deletions test/assets/webhook/mock.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,48 @@ info:
version: '1.0'

paths:
/login-fail:
post:
responses:
'500':
description: OK
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
example: false

/refreshToken:
post:
responses:
'200':
description: OK
content:
application/json:
schema:
type: object
properties:
refresh:
type: boolean
example: true

/redirectTo:
post:
responses:
'200':
description: OK
content:
application/json:
schema:
type: object
properties:
redirectTo:
type: string
example: api/test?q=str

/meta:
post:
responses:
Expand All @@ -20,7 +62,7 @@ paths:
properties:
bool:
type: boolean
x-faker: 'boolean(1)'
example: true
str:
type: string

Expand All @@ -36,7 +78,7 @@ paths:
properties:
success:
type: boolean
x-faker: 'boolean(0)'
example: false

/logout:
post:
Expand All @@ -50,7 +92,7 @@ paths:
properties:
success:
type: boolean
x-faker: 'boolean(1)'
example: true

/reject:
post:
Expand All @@ -64,4 +106,4 @@ paths:
properties:
reject:
type: boolean
x-faker: 'boolean(1)'
example: true

0 comments on commit b7b5c63

Please sign in to comment.