completed step 3 from the tutorial

you must login with an BTP account in order to see the app
This commit is contained in:
Markus Rettig 2024-02-08 16:13:36 +01:00
commit 775ac7b58c
6161 changed files with 601551 additions and 0 deletions

16
app1/node_modules/.bin/mime generated vendored Normal file
View file

@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../mime/cli.js" "$@"
else
exec node "$basedir/../mime/cli.js" "$@"
fi

17
app1/node_modules/.bin/mime.cmd generated vendored Normal file
View file

@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\mime\cli.js" %*

28
app1/node_modules/.bin/mime.ps1 generated vendored Normal file
View file

@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../mime/cli.js" $args
} else {
& "$basedir/node$exe" "$basedir/../mime/cli.js" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../mime/cli.js" $args
} else {
& "node$exe" "$basedir/../mime/cli.js" $args
}
$ret=$LASTEXITCODE
}
exit $ret

16
app1/node_modules/.bin/mustache generated vendored Normal file
View file

@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../mustache/bin/mustache" "$@"
else
exec node "$basedir/../mustache/bin/mustache" "$@"
fi

17
app1/node_modules/.bin/mustache.cmd generated vendored Normal file
View file

@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\mustache\bin\mustache" %*

28
app1/node_modules/.bin/mustache.ps1 generated vendored Normal file
View file

@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../mustache/bin/mustache" $args
} else {
& "$basedir/node$exe" "$basedir/../mustache/bin/mustache" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../mustache/bin/mustache" $args
} else {
& "node$exe" "$basedir/../mustache/bin/mustache" $args
}
$ret=$LASTEXITCODE
}
exit $ret

16
app1/node_modules/.bin/semver generated vendored Normal file
View file

@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../semver/bin/semver.js" "$@"
else
exec node "$basedir/../semver/bin/semver.js" "$@"
fi

17
app1/node_modules/.bin/semver.cmd generated vendored Normal file
View file

@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\semver\bin\semver.js" %*

28
app1/node_modules/.bin/semver.ps1 generated vendored Normal file
View file

@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../semver/bin/semver.js" $args
} else {
& "$basedir/node$exe" "$basedir/../semver/bin/semver.js" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../semver/bin/semver.js" $args
} else {
& "node$exe" "$basedir/../semver/bin/semver.js" $args
}
$ret=$LASTEXITCODE
}
exit $ret

16
app1/node_modules/.bin/uuid generated vendored Normal file
View file

@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../uuid/dist/bin/uuid" "$@"
else
exec node "$basedir/../uuid/dist/bin/uuid" "$@"
fi

17
app1/node_modules/.bin/uuid.cmd generated vendored Normal file
View file

@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\uuid\dist\bin\uuid" %*

28
app1/node_modules/.bin/uuid.ps1 generated vendored Normal file
View file

@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../uuid/dist/bin/uuid" $args
} else {
& "$basedir/node$exe" "$basedir/../uuid/dist/bin/uuid" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../uuid/dist/bin/uuid" $args
} else {
& "node$exe" "$basedir/../uuid/dist/bin/uuid" $args
}
$ret=$LASTEXITCODE
}
exit $ret

16
app1/node_modules/.bin/wtfnode generated vendored Normal file
View file

@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../wtfnode/proxy.js" "$@"
else
exec node "$basedir/../wtfnode/proxy.js" "$@"
fi

17
app1/node_modules/.bin/wtfnode.cmd generated vendored Normal file
View file

@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\wtfnode\proxy.js" %*

28
app1/node_modules/.bin/wtfnode.ps1 generated vendored Normal file
View file

@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../wtfnode/proxy.js" $args
} else {
& "$basedir/node$exe" "$basedir/../wtfnode/proxy.js" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../wtfnode/proxy.js" $args
} else {
& "node$exe" "$basedir/../wtfnode/proxy.js" $args
}
$ret=$LASTEXITCODE
}
exit $ret

2301
app1/node_modules/.package-lock.json generated vendored Normal file

File diff suppressed because it is too large Load diff

26
app1/node_modules/@colors/colors/LICENSE generated vendored Normal file
View file

@ -0,0 +1,26 @@
MIT License
Original Library
- Copyright (c) Marak Squires
Additional Functionality
- Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
- Copyright (c) DABH (https://github.com/DABH)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

219
app1/node_modules/@colors/colors/README.md generated vendored Normal file
View file

@ -0,0 +1,219 @@
# @colors/colors ("colors.js")
[![Build Status](https://github.com/DABH/colors.js/actions/workflows/ci.yml/badge.svg)](https://github.com/DABH/colors.js/actions/workflows/ci.yml)
[![version](https://img.shields.io/npm/v/@colors/colors.svg)](https://www.npmjs.org/package/@colors/colors)
Please check out the [roadmap](ROADMAP.md) for upcoming features and releases. Please open Issues to provide feedback.
## get color and style in your node.js console
![Demo](https://raw.githubusercontent.com/DABH/colors.js/master/screenshots/colors.png)
## Installation
npm install @colors/colors
## colors and styles!
### text colors
- black
- red
- green
- yellow
- blue
- magenta
- cyan
- white
- gray
- grey
### bright text colors
- brightRed
- brightGreen
- brightYellow
- brightBlue
- brightMagenta
- brightCyan
- brightWhite
### background colors
- bgBlack
- bgRed
- bgGreen
- bgYellow
- bgBlue
- bgMagenta
- bgCyan
- bgWhite
- bgGray
- bgGrey
### bright background colors
- bgBrightRed
- bgBrightGreen
- bgBrightYellow
- bgBrightBlue
- bgBrightMagenta
- bgBrightCyan
- bgBrightWhite
### styles
- reset
- bold
- dim
- italic
- underline
- inverse
- hidden
- strikethrough
### extras
- rainbow
- zebra
- america
- trap
- random
## Usage
By popular demand, `@colors/colors` now ships with two types of usages!
The super nifty way
```js
var colors = require('@colors/colors');
console.log('hello'.green); // outputs green text
console.log('i like cake and pies'.underline.red); // outputs red underlined text
console.log('inverse the color'.inverse); // inverses the color
console.log('OMG Rainbows!'.rainbow); // rainbow
console.log('Run the trap'.trap); // Drops the bass
```
or a slightly less nifty way which doesn't extend `String.prototype`
```js
var colors = require('@colors/colors/safe');
console.log(colors.green('hello')); // outputs green text
console.log(colors.red.underline('i like cake and pies')); // outputs red underlined text
console.log(colors.inverse('inverse the color')); // inverses the color
console.log(colors.rainbow('OMG Rainbows!')); // rainbow
console.log(colors.trap('Run the trap')); // Drops the bass
```
I prefer the first way. Some people seem to be afraid of extending `String.prototype` and prefer the second way.
If you are writing good code you will never have an issue with the first approach. If you really don't want to touch `String.prototype`, the second usage will not touch `String` native object.
## Enabling/Disabling Colors
The package will auto-detect whether your terminal can use colors and enable/disable accordingly. When colors are disabled, the color functions do nothing. You can override this with a command-line flag:
```bash
node myapp.js --no-color
node myapp.js --color=false
node myapp.js --color
node myapp.js --color=true
node myapp.js --color=always
FORCE_COLOR=1 node myapp.js
```
Or in code:
```javascript
var colors = require('@colors/colors');
colors.enable();
colors.disable();
```
## Console.log [string substitution](http://nodejs.org/docs/latest/api/console.html#console_console_log_data)
```js
var name = 'Beowulf';
console.log(colors.green('Hello %s'), name);
// outputs -> 'Hello Beowulf'
```
## Custom themes
### Using standard API
```js
var colors = require('@colors/colors');
colors.setTheme({
silly: 'rainbow',
input: 'grey',
verbose: 'cyan',
prompt: 'grey',
info: 'green',
data: 'grey',
help: 'cyan',
warn: 'yellow',
debug: 'blue',
error: 'red'
});
// outputs red text
console.log("this is an error".error);
// outputs yellow text
console.log("this is a warning".warn);
```
### Using string safe API
```js
var colors = require('@colors/colors/safe');
// set single property
var error = colors.red;
error('this is red');
// set theme
colors.setTheme({
silly: 'rainbow',
input: 'grey',
verbose: 'cyan',
prompt: 'grey',
info: 'green',
data: 'grey',
help: 'cyan',
warn: 'yellow',
debug: 'blue',
error: 'red'
});
// outputs red text
console.log(colors.error("this is an error"));
// outputs yellow text
console.log(colors.warn("this is a warning"));
```
### Combining Colors
```javascript
var colors = require('@colors/colors');
colors.setTheme({
custom: ['red', 'underline']
});
console.log('test'.custom);
```
*Protip: There is a secret undocumented style in `colors`. If you find the style you can summon him.*

View file

@ -0,0 +1,83 @@
var colors = require('../lib/index');
console.log('First some yellow text'.yellow);
console.log('Underline that text'.yellow.underline);
console.log('Make it bold and red'.red.bold);
console.log(('Double Raindows All Day Long').rainbow);
console.log('Drop the bass'.trap);
console.log('DROP THE RAINBOW BASS'.trap.rainbow);
// styles not widely supported
console.log('Chains are also cool.'.bold.italic.underline.red);
// styles not widely supported
console.log('So '.green + 'are'.underline + ' ' + 'inverse'.inverse
+ ' styles! '.yellow.bold);
console.log('Zebras are so fun!'.zebra);
//
// Remark: .strikethrough may not work with Mac OS Terminal App
//
console.log('This is ' + 'not'.strikethrough + ' fun.');
console.log('Background color attack!'.black.bgWhite);
console.log('Use random styles on everything!'.random);
console.log('America, Heck Yeah!'.america);
// eslint-disable-next-line max-len
console.log('Blindingly '.brightCyan + 'bright? '.brightRed + 'Why '.brightYellow + 'not?!'.brightGreen);
console.log('Setting themes is useful');
//
// Custom themes
//
console.log('Generic logging theme as JSON'.green.bold.underline);
// Load theme with JSON literal
colors.setTheme({
silly: 'rainbow',
input: 'grey',
verbose: 'cyan',
prompt: 'grey',
info: 'green',
data: 'grey',
help: 'cyan',
warn: 'yellow',
debug: 'blue',
error: 'red',
});
// outputs red text
console.log('this is an error'.error);
// outputs yellow text
console.log('this is a warning'.warn);
// outputs grey text
console.log('this is an input'.input);
console.log('Generic logging theme as file'.green.bold.underline);
// Load a theme from file
try {
colors.setTheme(require(__dirname + '/../themes/generic-logging.js'));
} catch (err) {
console.log(err);
}
// outputs red text
console.log('this is an error'.error);
// outputs yellow text
console.log('this is a warning'.warn);
// outputs grey text
console.log('this is an input'.input);
// console.log("Don't summon".zalgo)

View file

@ -0,0 +1,80 @@
var colors = require('../safe');
console.log(colors.yellow('First some yellow text'));
console.log(colors.yellow.underline('Underline that text'));
console.log(colors.red.bold('Make it bold and red'));
console.log(colors.rainbow('Double Raindows All Day Long'));
console.log(colors.trap('Drop the bass'));
console.log(colors.rainbow(colors.trap('DROP THE RAINBOW BASS')));
// styles not widely supported
console.log(colors.bold.italic.underline.red('Chains are also cool.'));
// styles not widely supported
console.log(colors.green('So ') + colors.underline('are') + ' '
+ colors.inverse('inverse') + colors.yellow.bold(' styles! '));
console.log(colors.zebra('Zebras are so fun!'));
console.log('This is ' + colors.strikethrough('not') + ' fun.');
console.log(colors.black.bgWhite('Background color attack!'));
console.log(colors.random('Use random styles on everything!'));
console.log(colors.america('America, Heck Yeah!'));
// eslint-disable-next-line max-len
console.log(colors.brightCyan('Blindingly ') + colors.brightRed('bright? ') + colors.brightYellow('Why ') + colors.brightGreen('not?!'));
console.log('Setting themes is useful');
//
// Custom themes
//
// console.log('Generic logging theme as JSON'.green.bold.underline);
// Load theme with JSON literal
colors.setTheme({
silly: 'rainbow',
input: 'blue',
verbose: 'cyan',
prompt: 'grey',
info: 'green',
data: 'grey',
help: 'cyan',
warn: 'yellow',
debug: 'blue',
error: 'red',
});
// outputs red text
console.log(colors.error('this is an error'));
// outputs yellow text
console.log(colors.warn('this is a warning'));
// outputs blue text
console.log(colors.input('this is an input'));
// console.log('Generic logging theme as file'.green.bold.underline);
// Load a theme from file
colors.setTheme(require(__dirname + '/../themes/generic-logging.js'));
// outputs red text
console.log(colors.error('this is an error'));
// outputs yellow text
console.log(colors.warn('this is a warning'));
// outputs grey text
console.log(colors.input('this is an input'));
// console.log(colors.zalgo("Don't summon him"))

184
app1/node_modules/@colors/colors/index.d.ts generated vendored Normal file
View file

@ -0,0 +1,184 @@
// Type definitions for @colors/colors 1.4+
// Project: https://github.com/Marak/colors.js
// Definitions by: Bart van der Schoor <https://github.com/Bartvds>, Staffan Eketorp <https://github.com/staeke>
// Definitions: https://github.com/DABH/colors.js
export interface Color {
(text: string): string;
strip: Color;
stripColors: Color;
black: Color;
red: Color;
green: Color;
yellow: Color;
blue: Color;
magenta: Color;
cyan: Color;
white: Color;
gray: Color;
grey: Color;
brightRed: Color;
brightGreen: Color;
brightYellow: Color;
brightBlue: Color;
brightMagenta: Color;
brightCyan: Color;
brightWhite: Color;
bgBlack: Color;
bgRed: Color;
bgGreen: Color;
bgYellow: Color;
bgBlue: Color;
bgMagenta: Color;
bgCyan: Color;
bgWhite: Color;
bgBrightRed: Color;
bgBrightGreen: Color;
bgBrightYellow: Color;
bgBrightBlue: Color;
bgBrightMagenta: Color;
bgBrightCyan: Color;
bgBrightWhite: Color;
reset: Color;
bold: Color;
dim: Color;
italic: Color;
underline: Color;
inverse: Color;
hidden: Color;
strikethrough: Color;
rainbow: Color;
zebra: Color;
america: Color;
trap: Color;
random: Color;
zalgo: Color;
}
export function enable(): void;
export function disable(): void;
export function setTheme(theme: any): void;
export let enabled: boolean;
export const strip: Color;
export const stripColors: Color;
export const black: Color;
export const red: Color;
export const green: Color;
export const yellow: Color;
export const blue: Color;
export const magenta: Color;
export const cyan: Color;
export const white: Color;
export const gray: Color;
export const grey: Color;
export const brightRed: Color;
export const brightGreen: Color;
export const brightYellow: Color;
export const brightBlue: Color;
export const brightMagenta: Color;
export const brightCyan: Color;
export const brightWhite: Color;
export const bgBlack: Color;
export const bgRed: Color;
export const bgGreen: Color;
export const bgYellow: Color;
export const bgBlue: Color;
export const bgMagenta: Color;
export const bgCyan: Color;
export const bgWhite: Color;
export const bgBrightRed: Color;
export const bgBrightGreen: Color;
export const bgBrightYellow: Color;
export const bgBrightBlue: Color;
export const bgBrightMagenta: Color;
export const bgBrightCyan: Color;
export const bgBrightWhite: Color;
export const reset: Color;
export const bold: Color;
export const dim: Color;
export const italic: Color;
export const underline: Color;
export const inverse: Color;
export const hidden: Color;
export const strikethrough: Color;
export const rainbow: Color;
export const zebra: Color;
export const america: Color;
export const trap: Color;
export const random: Color;
export const zalgo: Color;
declare global {
interface String {
strip: string;
stripColors: string;
black: string;
red: string;
green: string;
yellow: string;
blue: string;
magenta: string;
cyan: string;
white: string;
gray: string;
grey: string;
brightRed: string;
brightGreen: string;
brightYellow: string;
brightBlue: string;
brightMagenta: string;
brightCyan: string;
brightWhite: string;
bgBlack: string;
bgRed: string;
bgGreen: string;
bgYellow: string;
bgBlue: string;
bgMagenta: string;
bgCyan: string;
bgWhite: string;
bgBrightRed: string;
bgBrightGreen: string;
bgBrightYellow: string;
bgBrightBlue: string;
bgBrightMagenta: string;
bgBrightCyan: string;
bgBrightWhite: string;
reset: string;
// @ts-ignore
bold: string;
dim: string;
italic: string;
underline: string;
inverse: string;
hidden: string;
strikethrough: string;
rainbow: string;
zebra: string;
america: string;
trap: string;
random: string;
zalgo: string;
}
}

211
app1/node_modules/@colors/colors/lib/colors.js generated vendored Normal file
View file

@ -0,0 +1,211 @@
/*
The MIT License (MIT)
Original Library
- Copyright (c) Marak Squires
Additional functionality
- Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
var colors = {};
module['exports'] = colors;
colors.themes = {};
var util = require('util');
var ansiStyles = colors.styles = require('./styles');
var defineProps = Object.defineProperties;
var newLineRegex = new RegExp(/[\r\n]+/g);
colors.supportsColor = require('./system/supports-colors').supportsColor;
if (typeof colors.enabled === 'undefined') {
colors.enabled = colors.supportsColor() !== false;
}
colors.enable = function() {
colors.enabled = true;
};
colors.disable = function() {
colors.enabled = false;
};
colors.stripColors = colors.strip = function(str) {
return ('' + str).replace(/\x1B\[\d+m/g, '');
};
// eslint-disable-next-line no-unused-vars
var stylize = colors.stylize = function stylize(str, style) {
if (!colors.enabled) {
return str+'';
}
var styleMap = ansiStyles[style];
// Stylize should work for non-ANSI styles, too
if (!styleMap && style in colors) {
// Style maps like trap operate as functions on strings;
// they don't have properties like open or close.
return colors[style](str);
}
return styleMap.open + str + styleMap.close;
};
var matchOperatorsRe = /[|\\{}()[\]^$+*?.]/g;
var escapeStringRegexp = function(str) {
if (typeof str !== 'string') {
throw new TypeError('Expected a string');
}
return str.replace(matchOperatorsRe, '\\$&');
};
function build(_styles) {
var builder = function builder() {
return applyStyle.apply(builder, arguments);
};
builder._styles = _styles;
// __proto__ is used because we must return a function, but there is
// no way to create a function with a different prototype.
builder.__proto__ = proto;
return builder;
}
var styles = (function() {
var ret = {};
ansiStyles.grey = ansiStyles.gray;
Object.keys(ansiStyles).forEach(function(key) {
ansiStyles[key].closeRe =
new RegExp(escapeStringRegexp(ansiStyles[key].close), 'g');
ret[key] = {
get: function() {
return build(this._styles.concat(key));
},
};
});
return ret;
})();
var proto = defineProps(function colors() {}, styles);
function applyStyle() {
var args = Array.prototype.slice.call(arguments);
var str = args.map(function(arg) {
// Use weak equality check so we can colorize null/undefined in safe mode
if (arg != null && arg.constructor === String) {
return arg;
} else {
return util.inspect(arg);
}
}).join(' ');
if (!colors.enabled || !str) {
return str;
}
var newLinesPresent = str.indexOf('\n') != -1;
var nestedStyles = this._styles;
var i = nestedStyles.length;
while (i--) {
var code = ansiStyles[nestedStyles[i]];
str = code.open + str.replace(code.closeRe, code.open) + code.close;
if (newLinesPresent) {
str = str.replace(newLineRegex, function(match) {
return code.close + match + code.open;
});
}
}
return str;
}
colors.setTheme = function(theme) {
if (typeof theme === 'string') {
console.log('colors.setTheme now only accepts an object, not a string. ' +
'If you are trying to set a theme from a file, it is now your (the ' +
'caller\'s) responsibility to require the file. The old syntax ' +
'looked like colors.setTheme(__dirname + ' +
'\'/../themes/generic-logging.js\'); The new syntax looks like '+
'colors.setTheme(require(__dirname + ' +
'\'/../themes/generic-logging.js\'));');
return;
}
for (var style in theme) {
(function(style) {
colors[style] = function(str) {
if (typeof theme[style] === 'object') {
var out = str;
for (var i in theme[style]) {
out = colors[theme[style][i]](out);
}
return out;
}
return colors[theme[style]](str);
};
})(style);
}
};
function init() {
var ret = {};
Object.keys(styles).forEach(function(name) {
ret[name] = {
get: function() {
return build([name]);
},
};
});
return ret;
}
var sequencer = function sequencer(map, str) {
var exploded = str.split('');
exploded = exploded.map(map);
return exploded.join('');
};
// custom formatter methods
colors.trap = require('./custom/trap');
colors.zalgo = require('./custom/zalgo');
// maps
colors.maps = {};
colors.maps.america = require('./maps/america')(colors);
colors.maps.zebra = require('./maps/zebra')(colors);
colors.maps.rainbow = require('./maps/rainbow')(colors);
colors.maps.random = require('./maps/random')(colors);
for (var map in colors.maps) {
(function(map) {
colors[map] = function(str) {
return sequencer(colors.maps[map], str);
};
})(map);
}
defineProps(colors, init());

46
app1/node_modules/@colors/colors/lib/custom/trap.js generated vendored Normal file
View file

@ -0,0 +1,46 @@
module['exports'] = function runTheTrap(text, options) {
var result = '';
text = text || 'Run the trap, drop the bass';
text = text.split('');
var trap = {
a: ['\u0040', '\u0104', '\u023a', '\u0245', '\u0394', '\u039b', '\u0414'],
b: ['\u00df', '\u0181', '\u0243', '\u026e', '\u03b2', '\u0e3f'],
c: ['\u00a9', '\u023b', '\u03fe'],
d: ['\u00d0', '\u018a', '\u0500', '\u0501', '\u0502', '\u0503'],
e: ['\u00cb', '\u0115', '\u018e', '\u0258', '\u03a3', '\u03be', '\u04bc',
'\u0a6c'],
f: ['\u04fa'],
g: ['\u0262'],
h: ['\u0126', '\u0195', '\u04a2', '\u04ba', '\u04c7', '\u050a'],
i: ['\u0f0f'],
j: ['\u0134'],
k: ['\u0138', '\u04a0', '\u04c3', '\u051e'],
l: ['\u0139'],
m: ['\u028d', '\u04cd', '\u04ce', '\u0520', '\u0521', '\u0d69'],
n: ['\u00d1', '\u014b', '\u019d', '\u0376', '\u03a0', '\u048a'],
o: ['\u00d8', '\u00f5', '\u00f8', '\u01fe', '\u0298', '\u047a', '\u05dd',
'\u06dd', '\u0e4f'],
p: ['\u01f7', '\u048e'],
q: ['\u09cd'],
r: ['\u00ae', '\u01a6', '\u0210', '\u024c', '\u0280', '\u042f'],
s: ['\u00a7', '\u03de', '\u03df', '\u03e8'],
t: ['\u0141', '\u0166', '\u0373'],
u: ['\u01b1', '\u054d'],
v: ['\u05d8'],
w: ['\u0428', '\u0460', '\u047c', '\u0d70'],
x: ['\u04b2', '\u04fe', '\u04fc', '\u04fd'],
y: ['\u00a5', '\u04b0', '\u04cb'],
z: ['\u01b5', '\u0240'],
};
text.forEach(function(c) {
c = c.toLowerCase();
var chars = trap[c] || [' '];
var rand = Math.floor(Math.random() * chars.length);
if (typeof trap[c] !== 'undefined') {
result += trap[c][rand];
} else {
result += c;
}
});
return result;
};

110
app1/node_modules/@colors/colors/lib/custom/zalgo.js generated vendored Normal file
View file

@ -0,0 +1,110 @@
// please no
module['exports'] = function zalgo(text, options) {
text = text || ' he is here ';
var soul = {
'up': [
'̍', '̎', '̄', '̅',
'̿', '̑', '̆', '̐',
'͒', '͗', '͑', '̇',
'̈', '̊', '͂', '̓',
'̈', '͊', '͋', '͌',
'̃', '̂', '̌', '͐',
'̀', '́', '̋', '̏',
'̒', '̓', '̔', '̽',
'̉', 'ͣ', 'ͤ', 'ͥ',
'ͦ', 'ͧ', 'ͨ', 'ͩ',
'ͪ', 'ͫ', 'ͬ', 'ͭ',
'ͮ', 'ͯ', '̾', '͛',
'͆', '̚',
],
'down': [
'̖', '̗', '̘', '̙',
'̜', '̝', '̞', '̟',
'̠', '̤', '̥', '̦',
'̩', '̪', '̫', '̬',
'̭', '̮', '̯', '̰',
'̱', '̲', '̳', '̹',
'̺', '̻', '̼', 'ͅ',
'͇', '͈', '͉', '͍',
'͎', '͓', '͔', '͕',
'͖', '͙', '͚', '̣',
],
'mid': [
'̕', '̛', '̀', '́',
'͘', '̡', '̢', '̧',
'̨', '̴', '̵', '̶',
'͜', '͝', '͞',
'͟', '͠', '͢', '̸',
'̷', '͡', ' ҉',
],
};
var all = [].concat(soul.up, soul.down, soul.mid);
function randomNumber(range) {
var r = Math.floor(Math.random() * range);
return r;
}
function isChar(character) {
var bool = false;
all.filter(function(i) {
bool = (i === character);
});
return bool;
}
function heComes(text, options) {
var result = '';
var counts;
var l;
options = options || {};
options['up'] =
typeof options['up'] !== 'undefined' ? options['up'] : true;
options['mid'] =
typeof options['mid'] !== 'undefined' ? options['mid'] : true;
options['down'] =
typeof options['down'] !== 'undefined' ? options['down'] : true;
options['size'] =
typeof options['size'] !== 'undefined' ? options['size'] : 'maxi';
text = text.split('');
for (l in text) {
if (isChar(l)) {
continue;
}
result = result + text[l];
counts = {'up': 0, 'down': 0, 'mid': 0};
switch (options.size) {
case 'mini':
counts.up = randomNumber(8);
counts.mid = randomNumber(2);
counts.down = randomNumber(8);
break;
case 'maxi':
counts.up = randomNumber(16) + 3;
counts.mid = randomNumber(4) + 1;
counts.down = randomNumber(64) + 3;
break;
default:
counts.up = randomNumber(8) + 1;
counts.mid = randomNumber(6) / 2;
counts.down = randomNumber(8) + 1;
break;
}
var arr = ['up', 'mid', 'down'];
for (var d in arr) {
var index = arr[d];
for (var i = 0; i <= counts[index]; i++) {
if (options[index]) {
result = result + soul[index][randomNumber(soul[index].length)];
}
}
}
}
return result;
}
// don't summon him
return heComes(text, options);
};

View file

@ -0,0 +1,110 @@
var colors = require('./colors');
module['exports'] = function() {
//
// Extends prototype of native string object to allow for "foo".red syntax
//
var addProperty = function(color, func) {
String.prototype.__defineGetter__(color, func);
};
addProperty('strip', function() {
return colors.strip(this);
});
addProperty('stripColors', function() {
return colors.strip(this);
});
addProperty('trap', function() {
return colors.trap(this);
});
addProperty('zalgo', function() {
return colors.zalgo(this);
});
addProperty('zebra', function() {
return colors.zebra(this);
});
addProperty('rainbow', function() {
return colors.rainbow(this);
});
addProperty('random', function() {
return colors.random(this);
});
addProperty('america', function() {
return colors.america(this);
});
//
// Iterate through all default styles and colors
//
var x = Object.keys(colors.styles);
x.forEach(function(style) {
addProperty(style, function() {
return colors.stylize(this, style);
});
});
function applyTheme(theme) {
//
// Remark: This is a list of methods that exist
// on String that you should not overwrite.
//
var stringPrototypeBlacklist = [
'__defineGetter__', '__defineSetter__', '__lookupGetter__',
'__lookupSetter__', 'charAt', 'constructor', 'hasOwnProperty',
'isPrototypeOf', 'propertyIsEnumerable', 'toLocaleString', 'toString',
'valueOf', 'charCodeAt', 'indexOf', 'lastIndexOf', 'length',
'localeCompare', 'match', 'repeat', 'replace', 'search', 'slice',
'split', 'substring', 'toLocaleLowerCase', 'toLocaleUpperCase',
'toLowerCase', 'toUpperCase', 'trim', 'trimLeft', 'trimRight',
];
Object.keys(theme).forEach(function(prop) {
if (stringPrototypeBlacklist.indexOf(prop) !== -1) {
console.log('warn: '.red + ('String.prototype' + prop).magenta +
' is probably something you don\'t want to override. ' +
'Ignoring style name');
} else {
if (typeof(theme[prop]) === 'string') {
colors[prop] = colors[theme[prop]];
addProperty(prop, function() {
return colors[prop](this);
});
} else {
var themePropApplicator = function(str) {
var ret = str || this;
for (var t = 0; t < theme[prop].length; t++) {
ret = colors[theme[prop][t]](ret);
}
return ret;
};
addProperty(prop, themePropApplicator);
colors[prop] = function(str) {
return themePropApplicator(str);
};
}
}
});
}
colors.setTheme = function(theme) {
if (typeof theme === 'string') {
console.log('colors.setTheme now only accepts an object, not a string. ' +
'If you are trying to set a theme from a file, it is now your (the ' +
'caller\'s) responsibility to require the file. The old syntax ' +
'looked like colors.setTheme(__dirname + ' +
'\'/../themes/generic-logging.js\'); The new syntax looks like '+
'colors.setTheme(require(__dirname + ' +
'\'/../themes/generic-logging.js\'));');
return;
} else {
applyTheme(theme);
}
};
};

13
app1/node_modules/@colors/colors/lib/index.js generated vendored Normal file
View file

@ -0,0 +1,13 @@
var colors = require('./colors');
module['exports'] = colors;
// Remark: By default, colors will add style properties to String.prototype.
//
// If you don't wish to extend String.prototype, you can do this instead and
// native String will not be touched:
//
// var colors = require('@colors/colors/safe');
// colors.red("foo")
//
//
require('./extendStringPrototype')();

10
app1/node_modules/@colors/colors/lib/maps/america.js generated vendored Normal file
View file

@ -0,0 +1,10 @@
module['exports'] = function(colors) {
return function(letter, i, exploded) {
if (letter === ' ') return letter;
switch (i%3) {
case 0: return colors.red(letter);
case 1: return colors.white(letter);
case 2: return colors.blue(letter);
}
};
};

12
app1/node_modules/@colors/colors/lib/maps/rainbow.js generated vendored Normal file
View file

@ -0,0 +1,12 @@
module['exports'] = function(colors) {
// RoY G BiV
var rainbowColors = ['red', 'yellow', 'green', 'blue', 'magenta'];
return function(letter, i, exploded) {
if (letter === ' ') {
return letter;
} else {
return colors[rainbowColors[i++ % rainbowColors.length]](letter);
}
};
};

11
app1/node_modules/@colors/colors/lib/maps/random.js generated vendored Normal file
View file

@ -0,0 +1,11 @@
module['exports'] = function(colors) {
var available = ['underline', 'inverse', 'grey', 'yellow', 'red', 'green',
'blue', 'white', 'cyan', 'magenta', 'brightYellow', 'brightRed',
'brightGreen', 'brightBlue', 'brightWhite', 'brightCyan', 'brightMagenta'];
return function(letter, i, exploded) {
return letter === ' ' ? letter :
colors[
available[Math.round(Math.random() * (available.length - 2))]
](letter);
};
};

5
app1/node_modules/@colors/colors/lib/maps/zebra.js generated vendored Normal file
View file

@ -0,0 +1,5 @@
module['exports'] = function(colors) {
return function(letter, i, exploded) {
return i % 2 === 0 ? letter : colors.inverse(letter);
};
};

95
app1/node_modules/@colors/colors/lib/styles.js generated vendored Normal file
View file

@ -0,0 +1,95 @@
/*
The MIT License (MIT)
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
var styles = {};
module['exports'] = styles;
var codes = {
reset: [0, 0],
bold: [1, 22],
dim: [2, 22],
italic: [3, 23],
underline: [4, 24],
inverse: [7, 27],
hidden: [8, 28],
strikethrough: [9, 29],
black: [30, 39],
red: [31, 39],
green: [32, 39],
yellow: [33, 39],
blue: [34, 39],
magenta: [35, 39],
cyan: [36, 39],
white: [37, 39],
gray: [90, 39],
grey: [90, 39],
brightRed: [91, 39],
brightGreen: [92, 39],
brightYellow: [93, 39],
brightBlue: [94, 39],
brightMagenta: [95, 39],
brightCyan: [96, 39],
brightWhite: [97, 39],
bgBlack: [40, 49],
bgRed: [41, 49],
bgGreen: [42, 49],
bgYellow: [43, 49],
bgBlue: [44, 49],
bgMagenta: [45, 49],
bgCyan: [46, 49],
bgWhite: [47, 49],
bgGray: [100, 49],
bgGrey: [100, 49],
bgBrightRed: [101, 49],
bgBrightGreen: [102, 49],
bgBrightYellow: [103, 49],
bgBrightBlue: [104, 49],
bgBrightMagenta: [105, 49],
bgBrightCyan: [106, 49],
bgBrightWhite: [107, 49],
// legacy styles for colors pre v1.0.0
blackBG: [40, 49],
redBG: [41, 49],
greenBG: [42, 49],
yellowBG: [43, 49],
blueBG: [44, 49],
magentaBG: [45, 49],
cyanBG: [46, 49],
whiteBG: [47, 49],
};
Object.keys(codes).forEach(function(key) {
var val = codes[key];
var style = styles[key] = [];
style.open = '\u001b[' + val[0] + 'm';
style.close = '\u001b[' + val[1] + 'm';
});

View file

@ -0,0 +1,35 @@
/*
MIT License
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
'use strict';
module.exports = function(flag, argv) {
argv = argv || process.argv || [];
var terminatorPos = argv.indexOf('--');
var prefix = /^-{1,2}/.test(flag) ? '' : '--';
var pos = argv.indexOf(prefix + flag);
return pos !== -1 && (terminatorPos === -1 ? true : pos < terminatorPos);
};

View file

@ -0,0 +1,151 @@
/*
The MIT License (MIT)
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
'use strict';
var os = require('os');
var hasFlag = require('./has-flag.js');
var env = process.env;
var forceColor = void 0;
if (hasFlag('no-color') || hasFlag('no-colors') || hasFlag('color=false')) {
forceColor = false;
} else if (hasFlag('color') || hasFlag('colors') || hasFlag('color=true')
|| hasFlag('color=always')) {
forceColor = true;
}
if ('FORCE_COLOR' in env) {
forceColor = env.FORCE_COLOR.length === 0
|| parseInt(env.FORCE_COLOR, 10) !== 0;
}
function translateLevel(level) {
if (level === 0) {
return false;
}
return {
level: level,
hasBasic: true,
has256: level >= 2,
has16m: level >= 3,
};
}
function supportsColor(stream) {
if (forceColor === false) {
return 0;
}
if (hasFlag('color=16m') || hasFlag('color=full')
|| hasFlag('color=truecolor')) {
return 3;
}
if (hasFlag('color=256')) {
return 2;
}
if (stream && !stream.isTTY && forceColor !== true) {
return 0;
}
var min = forceColor ? 1 : 0;
if (process.platform === 'win32') {
// Node.js 7.5.0 is the first version of Node.js to include a patch to
// libuv that enables 256 color output on Windows. Anything earlier and it
// won't work. However, here we target Node.js 8 at minimum as it is an LTS
// release, and Node.js 7 is not. Windows 10 build 10586 is the first
// Windows release that supports 256 colors. Windows 10 build 14931 is the
// first release that supports 16m/TrueColor.
var osRelease = os.release().split('.');
if (Number(process.versions.node.split('.')[0]) >= 8
&& Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) {
return Number(osRelease[2]) >= 14931 ? 3 : 2;
}
return 1;
}
if ('CI' in env) {
if (['TRAVIS', 'CIRCLECI', 'APPVEYOR', 'GITLAB_CI'].some(function(sign) {
return sign in env;
}) || env.CI_NAME === 'codeship') {
return 1;
}
return min;
}
if ('TEAMCITY_VERSION' in env) {
return (/^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(env.TEAMCITY_VERSION) ? 1 : 0
);
}
if ('TERM_PROGRAM' in env) {
var version = parseInt((env.TERM_PROGRAM_VERSION || '').split('.')[0], 10);
switch (env.TERM_PROGRAM) {
case 'iTerm.app':
return version >= 3 ? 3 : 2;
case 'Hyper':
return 3;
case 'Apple_Terminal':
return 2;
// No default
}
}
if (/-256(color)?$/i.test(env.TERM)) {
return 2;
}
if (/^screen|^xterm|^vt100|^rxvt|color|ansi|cygwin|linux/i.test(env.TERM)) {
return 1;
}
if ('COLORTERM' in env) {
return 1;
}
if (env.TERM === 'dumb') {
return min;
}
return min;
}
function getSupportLevel(stream) {
var level = supportsColor(stream);
return translateLevel(level);
}
module.exports = {
supportsColor: getSupportLevel,
stdout: getSupportLevel(process.stdout),
stderr: getSupportLevel(process.stderr),
};

45
app1/node_modules/@colors/colors/package.json generated vendored Normal file
View file

@ -0,0 +1,45 @@
{
"name": "@colors/colors",
"description": "get colors in your node.js console",
"version": "1.6.0",
"author": "DABH",
"contributors": [
{
"name": "DABH",
"url": "https://github.com/DABH"
}
],
"homepage": "https://github.com/DABH/colors.js",
"bugs": "https://github.com/DABH/colors.js/issues",
"keywords": [
"ansi",
"terminal",
"colors"
],
"repository": {
"type": "git",
"url": "http://github.com/DABH/colors.js.git"
},
"license": "MIT",
"scripts": {
"lint": "eslint . --fix",
"test": "export FORCE_COLOR=1 && node tests/basic-test.js && node tests/safe-test.js"
},
"engines": {
"node": ">=0.1.90"
},
"main": "lib/index.js",
"files": [
"examples",
"lib",
"LICENSE",
"safe.js",
"themes",
"index.d.ts",
"safe.d.ts"
],
"devDependencies": {
"eslint": "^8.9.0",
"eslint-config-google": "^0.14.0"
}
}

64
app1/node_modules/@colors/colors/safe.d.ts generated vendored Normal file
View file

@ -0,0 +1,64 @@
// Type definitions for Colors.js 1.2
// Project: https://github.com/Marak/colors.js
// Definitions by: Bart van der Schoor <https://github.com/Bartvds>, Staffan Eketorp <https://github.com/staeke>
// Definitions: https://github.com/Marak/colors.js
export const enabled: boolean;
export function enable(): void;
export function disable(): void;
export function setTheme(theme: any): void;
export function strip(str: string): string;
export function stripColors(str: string): string;
export function black(str: string): string;
export function red(str: string): string;
export function green(str: string): string;
export function yellow(str: string): string;
export function blue(str: string): string;
export function magenta(str: string): string;
export function cyan(str: string): string;
export function white(str: string): string;
export function gray(str: string): string;
export function grey(str: string): string;
export function brightRed(str: string): string;
export function brightGreen(str: string): string;
export function brightYellow(str: string): string;
export function brightBlue(str: string): string;
export function brightMagenta(str: string): string;
export function brightCyan(str: string): string;
export function brightWhite(str: string): string;
export function bgBlack(str: string): string;
export function bgRed(str: string): string;
export function bgGreen(str: string): string;
export function bgYellow(str: string): string;
export function bgBlue(str: string): string;
export function bgMagenta(str: string): string;
export function bgCyan(str: string): string;
export function bgWhite(str: string): string;
export function bgBrightRed(str: string): string;
export function bgBrightGreen(str: string): string;
export function bgBrightYellow(str: string): string;
export function bgBrightBlue(str: string): string;
export function bgBrightMagenta(str: string): string;
export function bgBrightCyan(str: string): string;
export function bgBrightWhite(str: string): string;
export function reset(str: string): string;
export function bold(str: string): string;
export function dim(str: string): string;
export function italic(str: string): string;
export function underline(str: string): string;
export function inverse(str: string): string;
export function hidden(str: string): string;
export function strikethrough(str: string): string;
export function rainbow(str: string): string;
export function zebra(str: string): string;
export function america(str: string): string;
export function trap(str: string): string;
export function random(str: string): string;
export function zalgo(str: string): string;

10
app1/node_modules/@colors/colors/safe.js generated vendored Normal file
View file

@ -0,0 +1,10 @@
//
// Remark: Requiring this file will use the "safe" colors API,
// which will not touch String.prototype.
//
// var colors = require('colors/safe');
// colors.red("foo")
//
//
var colors = require('./lib/colors');
module['exports'] = colors;

View file

@ -0,0 +1,12 @@
module['exports'] = {
silly: 'rainbow',
input: 'grey',
verbose: 'cyan',
prompt: 'grey',
info: 'green',
data: 'grey',
help: 'cyan',
warn: 'yellow',
debug: 'blue',
error: 'red',
};

1509
app1/node_modules/@sap/approuter/CHANGELOG.md generated vendored Normal file

File diff suppressed because it is too large Load diff

38
app1/node_modules/@sap/approuter/LICENSE generated vendored Normal file
View file

@ -0,0 +1,38 @@
SAP DEVELOPER LICENSE AGREEMENT
Version 3.1
Please scroll down and read the following Developer License Agreement carefully ("Developer Agreement"). By clicking "I Accept" or by attempting to download, or install, or use the SAP software and other materials that accompany this Developer Agreement ("SAP Materials"), You agree that this Developer Agreement forms a legally binding agreement between You ("You" or "Your") and SAP SE, for and on behalf of itself and its subsidiaries and affiliates (as defined in Section 15 of the German Stock Corporation Act) and You agree to be bound by all of the terms and conditions stated in this Developer Agreement. If You are trying to access or download the SAP Materials on behalf of Your employer or as a consultant or agent of a third party (either "Your Company"), You represent and warrant that You have the authority to act on behalf of and bind Your Company to the terms of this Developer Agreement and everywhere in this Developer Agreement that refers to 'You' or 'Your' shall also include Your Company. If You do not agree to these terms, do not click "I Accept", and do not attempt to access or use the SAP Materials.
1. LICENSE: SAP grants You a non-exclusive, non-transferable, non-sublicensable, revocable, limited use license to copy, reproduce and distribute the application programming interfaces ("API"), documentation, plug-ins, templates, scripts and sample code ("Tools") on a desktop, laptop, tablet, smart phone, or other appropriate computer device that You own or control (any, a "Computer") to create new applications ("Customer Applications"). You agree that the Customer Applications will not: (a) unreasonably impair, degrade or reduce the performance or security of any SAP software applications, services or related technology ("Software"); (b) enable the bypassing or circumventing of SAP's license restrictions and/or provide users with access to the Software to which such users are not licensed; (c) render or provide, without prior written consent from SAP, any information concerning SAP software license terms, Software, or any other information related to SAP products; or (d) permit mass data extraction from an SAP product to a non-SAP product, including use, modification, saving or other processing of such data in the non-SAP product. In exchange for the right to develop Customer Applications under this Agreement, You covenant not to assert any Intellectual Property Rights in Customer Applications created by You against any SAP product, service, or future SAP development.
2. INTELLECTUAL PROPERTY: (a) SAP or its licensors retain all ownership and intellectual property rights in the APIs, Tools and Software. You may not: a) remove or modify any marks or proprietary notices of SAP, b) provide or make the APIs, Tools or Software available to any third party, c) assign this Developer Agreement or give or transfer the APIs, Tools or Software or an interest in them to another individual or entity, d) decompile, disassemble or reverse engineer (except to the extent permitted by applicable law) the APIs Tools or Software, (e) create derivative works of or based on the APIs, Tools or Software, (f) use any SAP name, trademark or logo, or (g) use the APIs or Tools to modify existing Software or other SAP product functionality or to access the Software or other SAP products' source code or metadata.
(b) Subject to SAP's underlying rights in any part of the APIs, Tools or Software, You retain all ownership and intellectual property rights in Your Customer Applications.
3. FREE AND OPEN SOURCE COMPONENTS: The SAP Materials may include certain third party free or open source components ("FOSS Components"). You may have additional rights in such FOSS Components that are provided by the third party licensors of those components.
4. THIRD PARTY DEPENDENCIES: The SAP Materials may require certain third party software dependencies ("Dependencies") for the use or operation of such SAP Materials. These dependencies may be identified by SAP in Maven POM files, product documentation or by other means. SAP does not grant You any rights in or to such Dependencies under this Developer Agreement. You are solely responsible for the acquisition, installation and use of Dependencies. SAP DOES NOT MAKE ANY REPRESENTATIONS OR WARRANTIES IN RESPECT OF DEPENDENCIES, INCLUDING BUT NOT LIMITED TO IMPLIED WARRANTIES OF MERCHANTABILITY AND OF FITNESS FOR A PARTICULAR PURPOSE. IN PARTICULAR, SAP DOES NOT WARRANT THAT DEPENDENCIES WILL BE AVAILABLE, ERROR FREE, INTEROPERABLE WITH THE SAP MATERIALS, SUITABLE FOR ANY PARTICULAR PURPOSE OR NON-INFRINGING. YOU ASSUME ALL RISKS ASSOCIATED WITH THE USE OF DEPENDENCIES, INCLUDING WITHOUT LIMITATION RISKS RELATING TO QUALITY, AVAILABILITY, PERFORMANCE, DATA LOSS, UTILITY IN A PRODUCTION ENVIRONMENT, AND NON-INFRINGEMENT. IN NO EVENT WILL SAP BE LIABLE DIRECTLY OR INDIRECTLY IN RESPECT OF ANY USE OF DEPENDENCIES BY YOU.
5. WARRANTY:
a) If You are located outside the US or Canada: AS THE API AND TOOLS ARE PROVIDED TO YOU FREE OF CHARGE, SAP DOES NOT GUARANTEE OR WARRANT ANY FEATURES OR QUALITIES OF THE TOOLS OR API OR GIVE ANY UNDERTAKING WITH REGARD TO ANY OTHER QUALITY. NO SUCH WARRANTY OR UNDERTAKING SHALL BE IMPLIED BY YOU FROM ANY DESCRIPTION IN THE API OR TOOLS OR ANY AVAILABLE DOCUMENTATION OR ANY OTHER COMMUNICATION OR ADVERTISEMENT. IN PARTICULAR, SAP DOES NOT WARRANT THAT THE SOFTWARE WILL BE AVAILABLE UNINTERRUPTED, ERROR FREE, OR PERMANENTLY AVAILABLE. FOR THE TOOLS AND API ALL WARRANTY CLAIMS ARE SUBJECT TO THE LIMITATION OF LIABILITY STIPULATED IN SECTION 4 BELOW.
b) If You are located in the US or Canada: THE API AND TOOLS ARE LICENSED TO YOU "AS IS", WITHOUT ANY WARRANTY, ESCROW, TRAINING, MAINTENANCE, OR SERVICE OBLIGATIONS WHATSOEVER ON THE PART OF SAP. SAP MAKES NO EXPRESS OR IMPLIED WARRANTIES OR CONDITIONS OF SALE OF ANY TYPE WHATSOEVER, INCLUDING BUT NOT LIMITED TO IMPLIED WARRANTIES OF MERCHANTABILITY AND OF FITNESS FOR A PARTICULAR PURPOSE. IN PARTICULAR, SAP DOES NOT WARRANT THAT THE SOFTWARE WILL BE AVAILABLE UNINTERRUPTED, ERROR FREE, OR PERMANENTLY AVAILABLE. YOU ASSUME ALL RISKS ASSOCIATED WITH THE USE OF THE API AND TOOLS, INCLUDING WITHOUT LIMITATION RISKS RELATING TO QUALITY, AVAILABILITY, PERFORMANCE, DATA LOSS, AND UTILITY IN A PRODUCTION ENVIRONMENT.
6. LIMITATION OF LIABILITY:
a) If You are located outside the US or Canada: IRRESPECTIVE OF THE LEGAL REASONS, SAP SHALL ONLY BE LIABLE FOR DAMAGES UNDER THIS AGREEMENT IF SUCH DAMAGE (I) CAN BE CLAIMED UNDER THE GERMAN PRODUCT LIABILITY ACT OR (II) IS CAUSED BY INTENTIONAL MISCONDUCT OF SAP OR (III) CONSISTS OF PERSONAL INJURY. IN ALL OTHER CASES, NEITHER SAP NOR ITS EMPLOYEES, AGENTS AND SUBCONTRACTORS SHALL BE LIABLE FOR ANY KIND OF DAMAGE OR CLAIMS HEREUNDER.
b) If You are located in the US or Canada: IN NO EVENT SHALL SAP BE LIABLE TO YOU, YOUR COMPANY OR TO ANY THIRD PARTY FOR ANY DAMAGES IN AN AMOUNT IN EXCESS OF $100 ARISING IN CONNECTION WITH YOUR USE OF OR INABILITY TO USE THE TOOLS OR API OR IN CONNECTION WITH SAP'S PROVISION OF OR FAILURE TO PROVIDE SERVICES PERTAINING TO THE TOOLS OR API, OR AS A RESULT OF ANY DEFECT IN THE API OR TOOLS. THIS DISCLAIMER OF LIABILITY SHALL APPLY REGARDLESS OF THE FORM OF ACTION THAT MAY BE BROUGHT AGAINST SAP, WHETHER IN CONTRACT OR TORT, INCLUDING WITHOUT LIMITATION ANY ACTION FOR NEGLIGENCE. YOUR SOLE REMEDY IN THE EVENT OF BREACH OF THIS DEVELOPER AGREEMENT BY SAP OR FOR ANY OTHER CLAIM RELATED TO THE API OR TOOLS SHALL BE TERMINATION OF THIS AGREEMENT. NOTWITHSTANDING ANYTHING TO THE CONTRARY HEREIN, UNDER NO CIRCUMSTANCES SHALL SAP AND ITS LICENSORS BE LIABLE TO YOU OR ANY OTHER PERSON OR ENTITY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, OR INDIRECT DAMAGES, LOSS OF GOOD WILL OR BUSINESS PROFITS, WORK STOPPAGE, DATA LOSS, COMPUTER FAILURE OR MALFUNCTION, ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSS, OR EXEMPLARY OR PUNITIVE DAMAGES.
7. INDEMNITY: You will fully indemnify, hold harmless and defend SAP against law suits based on any claim: (a) that any Customer Application created by You infringes or misappropriates any patent, copyright, trademark, trade secrets, or other proprietary rights of a third party, or (b) related to Your alleged violation of the terms of this Developer Agreement.
8. EXPORT: The Tools and API are subject to German, EU and US export control regulations. You confirm that: a) You will not use the Tools or API for, and will not allow the Tools or API to be used for, any purposes prohibited by German, EU and US law, including, without limitation, for the development, design, manufacture or production of nuclear, chemical or biological weapons of mass destruction; b) You are not located in Cuba, Iran, Sudan, Iraq, North Korea, Syria, nor any other country to which the United States has prohibited export or that has been designated by the U.S. Government as a "terrorist supporting" country (any, an "US Embargoed Country"); c) You are not a citizen, national or resident of, and are not under the control of, a US Embargoed Country; d) You will not download or otherwise export or re-export the API or Tools, directly or indirectly, to a US Embargoed Country nor to citizens, nationals or residents of a US Embargoed Country; e) You are not listed on the United States Department of Treasury lists of Specially Designated Nationals, Specially Designated Terrorists, and Specially Designated Narcotic Traffickers, nor listed on the United States Department of Commerce Table of Denial Orders or any other U.S. government list of prohibited or restricted parties and f) You will not download or otherwise export or re-export the API or Tools , directly or indirectly, to persons on the above-mentioned lists.
9. SUPPORT: Other than what is made available on the SAP Community Website (SCN) by SAP at its sole discretion and by SCN members, SAP does not offer support for the API or Tools which are the subject of this Developer Agreement.
10. TERM AND TERMINATION: You may terminate this Developer Agreement by destroying all copies of the API and Tools on Your Computer(s). SAP may terminate Your license to use the API and Tools immediately if You fail to comply with any of the terms of this Developer Agreement, or, for SAP's convenience by providing you with ten (10) day's written notice of termination (including email). In case of termination or expiration of this Developer Agreement, You must destroy all copies of the API and Tools immediately. In the event Your Company or any of the intellectual property you create using the API, Tools or Software are acquired (by merger, purchase of stock, assets or intellectual property or exclusive license), or You become employed, by a direct competitor of SAP, then this Development Agreement and all licenses granted in this Developer Agreement shall immediately terminate upon the date of such acquisition.
11. LAW/VENUE:
a) If You are located outside the US or Canada: This Developer Agreement is governed by and construed in accordance with the laws of the Germany. You and SAP agree to submit to the exclusive jurisdiction of, and venue in, the courts of Karlsruhe in Germany in any dispute arising out of or relating to this Developer Agreement.
b) If You are located in the US or Canada: This Developer Agreement shall be governed by and construed under the Commonwealth of Pennsylvania law without reference to its conflicts of law principles. In the event of any conflicts between foreign law, rules, and regulations, and United States of America law, rules, and regulations, United States of America law, rules, and regulations shall prevail and govern. The United Nations Convention on Contracts for the International Sale of Goods shall not apply to this Developer Agreement. The Uniform Computer Information Transactions Act as enacted shall not apply.
12. MISCELLANEOUS: This Developer Agreement is the complete agreement for the API and Tools licensed (including reference to information/documentation contained in a URL). This Developer Agreement supersedes all prior or contemporaneous agreements or representations with regards to the subject matter of this Developer Agreement. If any term of this Developer Agreement is found to be invalid or unenforceable, the surviving provisions shall remain effective. SAP's failure to enforce any right or provisions stipulated in this Developer Agreement will not constitute a waiver of such provision, or any other provision of this Developer Agreement.

2396
app1/node_modules/@sap/approuter/README.md generated vendored Normal file

File diff suppressed because it is too large Load diff

148
app1/node_modules/@sap/approuter/approuter.js generated vendored Normal file
View file

@ -0,0 +1,148 @@
'use strict';
const assert = require('assert');
const commander = require('commander');
const _ = require('lodash');
const EventEmitter = require('events');
const util = require('util');
const bootstrap = require('./lib/bootstrap');
const loggerUtil = require('./lib/utils/logger');
const validators = require('./lib/configuration/validators');
const appConfig = require('./lib/configuration/app-config');
const envConfig = require('./lib/configuration/env-config');
const MiddlewareList = require('./lib/extensions/MiddlewareList');
const serverLib = require('./lib/server');
const optionalCallback = require('./lib/utils/callback');
const uaaUtils = require('./lib/utils/uaa-utils');
const dynamicRoutingUtils = require('./lib/utils/dynamic-routing-utils');
module.exports = Approuter;
function Approuter() {
assert(arguments.length === 0, 'Constructor takes no arguments');
if (!(this instanceof Approuter)) {
return new Approuter();
}
EventEmitter.call(this);
this.cmdParser = new commander.Command()
.option('-w, --workingDir <s>', 'The working directory containting the resources folder and xs-app.json file')
.option('-p, --port <n>', 'The port of the approuter');
this.first = new MiddlewareList();
this.beforeRequestHandler = new MiddlewareList();
this.beforeErrorHandler = new MiddlewareList();
}
util.inherits(Approuter, EventEmitter);
Approuter.prototype.start = function (options, callback) {
let self = this;
if (dynamicRoutingUtils.isDynamicRouting()) {
self.first.use(dynamicRoutingUtils.initialize(self));
if (options) {
delete options.port;
options.getRouterConfig = dynamicRoutingUtils.getRouterConfig;
} else {
options = {'getRouterConfig': dynamicRoutingUtils.getRouterConfig};
}
}
if (options) {
validators.validateApprouterStartOptions(options);
options = _.cloneDeep(options);
} else {
options = {};
}
callback = optionalCallback(callback);
if (this.cmdParser) {
this.cmdParser.parse(process.argv);
options = _.defaults(options, this.cmdParser);
}
addImplicitExtension(this, options);
let logger = loggerUtil.getLogger('/approuter');
logger.info('Application router version %s', require('./package.json').version);
let app = bootstrap(options);
app.logger = logger;
app.approuter = this;
this._app = app;
loggerUtil.getAuditLogger(function(err, auditLogger){
if (err) {
throw err;
}
app.auditLogger = auditLogger;
serverLib.start(app, function (err, server) {
self._server = server;
callback(err);
});
});
};
Approuter.prototype.close = function (callback) {
if (this._server) {
clearInterval(this._app._store.memoryStore.sessionChecker);
this._server.close(callback);
} else {
process.nextTick(callback);
}
};
Approuter.prototype.resolveUaaConfig = uaaUtils.resolveUaaConfig;
Approuter.prototype.getRemoteConfigurationOptions = dynamicRoutingUtils.getRemoteConfigurationOptions;
Approuter.prototype.createRouterConfig = function (options, callback) {
options = _.cloneDeep(options); // avoid side effects in case of modifications
let error;
let routerConfig;
try {
assert(this._app, 'Call Approuter.start before Approuter.createRouterConfig');
routerConfig = this._app.get('mainRouterConfig');
let xsappname = options.xsappname || routerConfig.uaaConfig.options.xsappname;
let customDestinations = (options.destinations) ? envConfig.loadDestinations(options.destinations) : routerConfig.destinations;
let customAppConfig = appConfig.loadConfiguration(routerConfig.workingDir,
options.xsappConfig, customDestinations, xsappname);
customAppConfig.logout = _.extend({}, routerConfig.appConfig.logout, _.pick(customAppConfig.logout, 'logoutPage', 'logoutEndpoint', 'logoutMethod', 'csrfProtection'));
routerConfig = _.defaults({
appConfig: customAppConfig,
destinations: customDestinations
}, routerConfig);
} catch (err) {
error = err;
}
callback(error, routerConfig);
};
Approuter.prototype.getSessionStore = function getSessionStore() {
let memoryStore = this._app.get('memoryStore');
return Object.create(memoryStore, {
set: {
value: function(sessionId, sessionString, timeout, callback) {
let diff = timeout - Date.now();
memoryStore.set(sessionId, JSON.parse(sessionString), callback);
memoryStore.sessionTimers[sessionId] = Math.floor(diff / 10000); // tens of seconds
}
}
});
};
function addImplicitExtension(ar, options) {
let ext = {
insertMiddleware: {
first: ar.first._middleware,
beforeRequestHandler: ar.beforeRequestHandler._middleware,
beforeErrorHandler: ar.beforeErrorHandler._middleware
}
};
options.extensions = options.extensions || [];
options.extensions.unshift(ext);
}
if (require.main === module) {
let ar = new Approuter();
ar.start();
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

368
app1/node_modules/@sap/approuter/doc/extending.md generated vendored Normal file
View file

@ -0,0 +1,368 @@
Extending Application Router
============================
<!-- toc -->
- [Basics](#basics)
- [Inject Custom Middleware](#inject-custom-middleware)
- [Application Router Extensions](#application-router-extensions)
- [Customize Command Line](#customize-command-line)
- [Dynamic Routing](#dynamic-routing)
- [State synchronization](#state-synchronization)
- [API Reference](#api-reference)
* [approuter](#approuter)
+ [`approuter()`](#approuter)
+ [Event: 'login'](#event-login)
+ [Event: 'logout'](#event-logout)
+ [`first`](#first)
+ [`beforeRequestHandler`](#beforerequesthandler)
+ [`afterRequestHandler`](#afterrequesthandler)
+ [`backendTimeout`](#backendtimeout)
+ [`beforeErrorHandler`](#beforeerrorhandler)
+ [`start(options, callback)`](#startoptions-callback)
+ [`close(callback)`](#closecallback)
+ [`createRouterConfig(options, callback)`](#createrouterconfigoptions-callback)
+ [`resolveUaaConfig(request, uaaOptions, callback)`](#resolveuaaconfigrequest-uaaoptions-callback)
* [Middleware Slot](#middleware-slot)
+ [`use(path, handler)`](#usepath-handler)
<!-- tocstop -->
## Basics
Insead of starting the application router directly, your application can have its own start script.
You can use the application router as a regular Node.js package.
```js
var approuter = require('@sap/approuter');
var ar = approuter();
ar.start();
```
## Inject Custom Middleware
The application router uses the [connect](https://github.com/senchalabs/connect)
framework.
You can reuse all _connect_ middlewares within the application router directly.
You can do this directly in your start script:
```js
var approuter = require('@sap/approuter');
var ar = approuter();
ar.beforeRequestHandler.use('/my-ext', function myMiddleware(req, res, next) {
res.end('Request handled by my extension!');
});
ar.start();
```
__Tip:__ Name your middleware to improve troubleshooting.
The path argument is optional. You can also chain `use` calls.
```js
var approuter = require('@sap/approuter');
var morgan = require('morgan');
var ar = approuter();
ar.beforeRequestHandler
.use(morgan('combined'))
.use('/my-ext', function myMiddleware(req, res, next) {
res.end('Request handled by my extension!');
});
ar.start();
```
The application router defines the following slots where you can insert custom middleware:
* `first` - right after the _connect_ application is created, and before any
application router middleware.
At this point security checks are not performed yet.
__Tip:__ This is a good place for infrastructure logic like logging and monitoring.
* `beforeRequestHandler` - before standard application router request handling,
that is static resource serving or forwarding to destinations.
__Tip:__ This is a good place for custom REST API handling.
* `beforeErrorHandler` - before standard application router error handling.
__Tip:__ This is a good place to capture or customize error handling.
If your middleware does not complete the request processing, call `next`
to return control to the application router middleware:
```js
ar.beforeRequestHandler.use('/my-ext', function myMiddleware(req, res, next) {
res.setHeader('x-my-ext', 'passed');
next();
});
```
## Application Router Extensions
You can use application router extensions.
An extension is defined by an object with the following properties:
* `insertMiddleware` - describes the middleware provided by this extension
* `first`, `beforeRequestHandler`, `beforeErrorHandler` - an array of middleware, where each one is either
* a middleware function (invoked on all requests), or
* an object with properties:
* `path` - handle requests only for this path
* `handler` - middleware function to invoke
Here is an example (my-ext.js):
```js
module.exports = {
insertMiddleware: {
first: [
function logRequest(req, res, next) {
console.log('Got request %s %s', req.method, req.url);
}
],
beforeRequestHandler: [
{
path: '/my-ext',
handler: function myMiddleware(req, res, next) {
res.end('Request handled by my extension!');
}
}
]
}
};
```
You can use it in your start script like this:
```js
var approuter = require('@sap/approuter');
var ar = approuter();
ar.start({
extensions: [
require('./my-ext.js')
]
});
```
## Customize Command Line
By default the application router handles its command line parameters, but you can
customize that too.
An _approuter_ instance provides the property `cmdParser` that is a
[commander](https://github.com/tj/commander.js/) instance.
It is configured with the standard application router command line options.
There you can add custom options like this:
```js
var approuter = require('@sap/approuter');
var ar = approuter();
var params = ar.cmdParser
// add here custom command line options if needed
.option('-d, --dummy', 'A dummy option')
.parse(process.argv);
console.log('Dummy option:', params.dummy);
```
To completely disable the command line option handling in the application router,
reset the following property:
```js
ar.cmdParser = false;
```
## Dynamic Routing
The application router can use a custom routing configuration for each request.
Here is an example:
```js
var approuter = require('@sap/approuter');
var ar = approuter();
ar.start({
getRouterConfig: getRouterConfig
});
var customRouterConfig;
var options = {
xsappConfig: {
routes: [
{
source: '/service',
destination: 'backend',
scope: '$XSAPPNAME.viewer',
}
]
},
destinations: [
{
name: 'backend',
url: 'https://my.app.com',
forwardAuthToken: true
}
],
xsappname: 'MYAPP'
};
ar.createRouterConfig(options, function(err, routerConfig) {
if (err) {
console.error(err);
} else {
customRouterConfig = routerConfig;
}
});
function getRouterConfig(request, callback) {
if (/\?custom-query/.test(request.url)) {
callback(null, customRouterConfig);
} else {
callback(null, null); // use default router config
}
}
```
## State synchronization
The application router can be scaled to run with multiple instances like any other application on Cloud Foundry.
Still application router instances are not aware of each other and there is no communication among them.
So if extensions introduce some state, they should take care to synchronize it across application router instances.
## API Reference
### approuter
#### `approuter()`
Creates a new instance of the application router.
#### Event: 'login'
Parameters:
* `session`
* `id` - session id as a string
Emitted when a new user session is created.
#### Event: 'logout'
Parameters:
* `session`
* `id` - session id as a string
Emitted when a user session has expired or a user has requested to log out.
#### `first`
A [Middleware Slot](#middleware-slot) before the first application router middleware
#### `beforeRequestHandler`
A [Middleware Slot](#middleware-slot) before the standard application router request handling
#### `afterRequestHandler`
A function that can be added to the request object - for example in a "first" or "beforeRequestHandler" extension.
If exists, this function will be called by the standard application router after the standard backend response handling is completed.
Input:
* `ctx` context object containing the following properties
* `incomingRequest` the request sent from client to application router
* `incomingResponse` the response that will be sent from application router to client
* `outgoingRequest` the request sent from application router to backend application
* `outgoingResponse` the response that was received in application router from backend application
* `done` a callback function that receives (optionally) and error and the modified incomingResponse
Note that this function is called after standard application router headers processing. Data piping is not modified.
If an error is passed to done callback it will be just logged, piping process will not be stopped. Note that also in case of error the incomingResponse object should be returned.
Example:
```js
var approuter = require('@sap/approuter');
var ar = approuter();
ar.first.use('/backend', function (req, res, next) {
req.afterRequestHandler = function(ctx, done){
if (ctx.outgoingResponse.statusCode === 200) {
let incomingResponse = ctx.incomingResponse;
incomingResponse.setHeader('header1', 'abc');
done(null, incomingResponse);
} else {
done('An error occurred in backend, returned status ' + ctx.outgoingResponse.statusCode, ctx.incomingResponse);
}
};
next();
});
ar.start();
```
#### `backendTimeout`
A function that can be added to the request object - for example in a "first" or "beforeRequestHandler" extension.
If exists, this function will be called by the standard application router when a backend connection timeout occurs.
Input:
* `req` the request object
* `done` a callback function that doesn't return any parameter
#### `beforeErrorHandler`
A [Middleware Slot](#middleware-slot) before the standard application router error handling
#### `start(options, callback)`
Starts the application router with the given options.
* `options` this argument is optional. If provided, it should be an object which can have any of the following properties:
* `port` - a TCP port the application router will listen to (string, optional)
* `workingDir` - the working directory for the application router,
should contain the _xs-app.json_ file (string, optional)
* `extensions` - an array of extensions, each one is an object as defined in
[Application Router Extensions](#application-router-extensions) (optional)
* `xsappConfig` - An object representing the content which is usually put in xs-app.json file.
If this property is present it will take precedence over the content of xs-app.json. (optional)
* `httpsOptions` - Options similar to [`https.createServer`](https://nodejs.org/api/https.html#https_https_createserver_options_requestlistener).
If this property is present, application router will be started as an https server. (optional)
* `getToken` - `function(request, callback)` Provide custom access token (optional)
* `request` - Node request object
* `callback` - `function(error, token)`
* `error` - Error object in case of error
* `token` - Access token to use in request to backend
* `getRouterConfig` - `function(request, callback)` Provide custom routing configuration (optional)
* `request` - Node request object
* `callback` - `function(error, routerConfig)`
* `error` - Error object in case of error
* `routerConfig` - Custom routing configuration to use for given request.
This object should be created via `createRouterConfig`.
If `null` or `undefined`, default configuration will be used.
**Note:** When approuter is bound to html5 repository, you cannot provide getRouterConfig function.
* `callback` - optional function with signature `callback(err)`.
It is invoked when the application router has started or an error has occurred.
If not provided and an error occurs (e.g. the port is busy), the application will abort.
#### `close(callback)`
Stops the application router.
* `callback` - optional function with signature `callback(err)`.
It is invoked when the application router has stopped or an error has occurred.
#### `createRouterConfig(options, callback)`
Prepares the routing configuration to be used by the application router.
As part of this, the application router validates the given options.
This function can be used at any point in runtime to create additional routing configurations.
**Note:** This function can be called only after `start` function.
* `options`
* `xsappname` - Value to replace $XSAPPNAME placeholder in scope names.
If not provided, it will be taken from UAA service binding. (optional)
* `xsappConfig` - An object representing the content which is usually put in xs-app.json file.
**Note:** Only the following configurations are taken into account from this property (the rest are taken from the xs-app.json file):
`welcomeFile`, `logout.logoutEndpoint`, `logout.logoutPage`, `routes`, `websockets`, `errorPage`.
* `destinations` - An array containing the configuration of the backend destinations.
If not provided, it will be taken from `destinations` environment variable. (optional)
* `callback` - `function(error, routerConfig)`
* `error` - Error object in case of error
* `routerConfig` - Routing configuration to be passed to the callback of `getRouterConfig`.
Approuter extensions should not access the content of this object.
#### `resolveUaaConfig(request, uaaOptions, callback)`
Calculates tenant-specific UAA configuration.
* `request` - node request object used to identify the tenant
* `uaaOptions` - UAA options as provided in service binding
* `callback` - `function(error, tenantUaaOptions)`
* `error` - Error object in case of error
* `tenantUaaOptions` - new UAA configuration with tenant-specific properties
### Middleware Slot
#### `use(path, handler)`
Inserts a request handling middleware in the current slot.
* `path` - handle only requests starting with this path (string, optional)
* `handler` - a middleware function to invoke (function, mandatory)
Returns `this` for chaining.

View file

@ -0,0 +1,509 @@
Extended Session Management
===========================
<!-- toc -->
- [Abstract](#abstract)
- [Session Lifecycle](#session-lifecycle)
- [Security](#security)
- [Data Privacy](#data-privacy)
- [API Reference](#api-reference)
- [Example](#example)
- [Performance](#performance)
- [Custom Storage Driver](#custom-storage-driver)
- [Credentials Structure](#credentials-structure)
<!-- tocstop -->
## Abstract
The application router uses a memory store as a session repository to provide
the best runtime performance. However, it is not persisted and it is not shared
across multiple instances of the application router.
*__Note:__ The Limitations above do not prevent the application router from
being scaled out, since session stickiness is in place by default.*
While it is good enough for most of the cases, it may be required to
provide a highly-available solution, which may be achieved by storing
the state (session) of the application router outside - in durable shared
storage.
To allow implementing these qualities, the application router exposes the
*extended session management* API described below.
## Session Lifecycle
The application router stores user agent sessions as JavaScript objects
serialized to strings. It also stores the session timeout associated with
each session, which indicates the amount of time left until session
invalidation.
### Initial Data
During the start of the application router, the internal session store is initiated.
It contains an empty list of sessions and their timeouts. The internal session
store is not available right after the application router instance is
created, but is available in the callback of `approuter.start` and all
the time afterwards until the application router is stopped.
In case an external session storage is used, the application router extension
should perform the following actions to synchronize the internal session
store with the external one:
- Load existing sessions from external storage
- Start the application router
- Populate the application router's internal session store
### Read
A session identifier may be obtained from the request object `req.sessionID`.
On each request, the application router executes registered middlewares
in a certain order and the session is not available to all of them.
- First it passes the request to `approuter.first` middleware.
At this point, there is no session associated with
the incoming request.
- Afterwards, the application router checks if the user is authenticated, reads
the relevant session from the internal session store and puts it into the request
context.
- Next, the application router passes a request to
`approuter.broforeRequestHandler`. At this point, the session object is
available and associated with the incoming request.
- `approuter.beforeErrorHandler` also has access to session.
### Login
When a user agent requests a resource, served via a route that requires
authentication, the application router will request the user agent to
pass authentication first (usually via redirect to XSUAA). At this point,
the application router does not create any session. Only after
the authentication process is finished, the application router creates a session,
stores it in the internal session storage and emits a `login` event.
### Update Session
Any changes made to the session are not stored in the internal session store
immediately, but are accumulated to make a bulk update after the end of the response.
While the request is passed through the chain of middlewares, the session object
may be modified. Also, when the backend access token is close to expire,
the application router may trigger the refresh backend token flow. In both cases,
the actual update of the internal session store is done later on, outside of
the request context.
### Timeout
There is a time-based job in the application router that basis outside
the request context and destroys sessions with an elapsed timeout.
Each time the application router reads a session from the session store,
the timeout of this session is reset to the initial value that may be retrieved
using the [`getDefaultSessionTimeout()`](#sessionstoregetdefaultsessiontimeout)
API.
### Logout
When a user agent requests a URL defined as the `logoutEndpoint` in the
`xs-app.json` file, a central logout process takes place. As part of this
process, the application router emits a `logout` event. More detailed
information about the central logout may be found in
[README.md](../README.md)
## Security
The application router uses session secret to sign session cookies and
prevent tampering. The session secret, by default, is generated using
a random sequence of bytes at the startup of the application router. It is
different for each instance and changed on each restart of the same
instance.
Using the default session secret generation mechanism for highly available
application routers may cause issues in the following scenarios:
- The user agent is authenticated and the session is stored in a session store.
The application router is restarted (due to internal error or triggered
by platform) and a new session secret is generated. The authenticated user
agent makes a request, which contains the session cookies. However, the cookies are
signed using another secret and the application router ignores them.
- The user agent is authenticated and the session is stored in the session store.
The application router instance is unavailable. The authenticated user agent
makes a request to the application router and the request contains the session
cookies. The load balancer forwards the request to another instance of
the application router. However, cookies are signed using another secret and
the application router ignores them.
In both scenarios, the session in the store is no longer accessible, the cookies
sent by the user agent are redundant, and the user agent will be requested to
pass authentication once again.
To avoid the issues described above, the extension that implements the extended session
management mechanism, should make sure to implement the `getSessionSecret` hook.
```js
var ar = AppRouter();
ar.start({
getSessionSecret: function () {
return 'CUSTOM_PERSISTED_SESSION_SECRET';
},
...
});
```
It is recommended to have at least 128 characters in the string that replaces
`CUSTOM_PERSISTED_SESSION_SECRET`.
## Data Privacy
The user agent session potentially contains personal data. By implementing
the custom session management behaviour, you take the responsibility to be
compliant with all personal data protection laws and regulations
(e.g. [GDPR](https://en.wikipedia.org/wiki/General_Data_Protection_Regulation))
that may be applied in the regions, where the application will be used.
## API Reference
### Methods
#### approuter.start(options)
* `options`
* `getSessionSecret` - returns the session secret to be used
by the application router for the signing of the session cookies.
#### approuter.getSessionStore()
returns `SessionStore` instance.
#### sessionStore.getDefaultSessionTimeout()
returns the default session timeout in minutes.
#### sessionStore.getSessionTimeout(sessionId, callback)
* `sessionId` - an unsigned session identifier
* `callback` - `function(error, session)` a function that is called
when the session object is retrieved from the internal session
storage of the application router.
* `error` - an error object in case of an error, otherwise `null`
* `timeout` - time, in minutes, until the session times out
#### sessionStore.get(sessionId, callback)
* `sessionId` - an unsigned session identifier
* `callback` - `function(error, session)` a function that is called
when the session object is retrieved from the internal session
storage of the application router.
* `error` - an error object in case of an error, otherwise `null`
* `session` - the session object
* `id` - session identifier, immutable
#### sessionStore.set(sessionId, sessionString, timeout, callback)
* `sessionId` - an unsigned session identifier
* `sessionString` - a session object serialized to string
* `timeout` - a timestamp in milliseconds, after which the session should be
automatically invalidated
* `callback` - a function that is called after the session is saved in the
internal session storage of the application router
#### sessionStore.update(sessionId, callback, resetTimeout)
* `sessionId` - an unsigned session identifier
* `callback` - `function(currentSession)` function, which returns
session object. Callback function may modify and return current
session object or create and return brand new session object
* `currentSession` - current session object
* `resetTimeout` - a boolean that indicates whether to reset the session timeout
#### sessionStore.destroy(sessionId, callback)
* `sessionId` - an unsigned session identifier
* `callback` - a function that is called after the session is destroyed in
the internal session storage of the application router
### Events
Extension may subscribe to application router events using the standard
[`EventEmitter`](https://nodejs.org/api/events.html) API.
```js
var ar = AppRouter();
ar.on('someEvent', function handler() {
// Handle event
});
```
#### `login`
Emitted when user agent is authenticated.
Parameters:
* `session` - session object
* `id` - session identifier, immutable
#### `logout`
Emitted when a user agent session is going to be terminated in
the internal session store of the application router. Emitted either when
the user agent session is timed-out or when `logoutEndpoint` was requested.
*__Note:__ Central logout is an asynchronous process. The order in which
the backend and the application router sessions are invalidated, is not
guaranteed.*
Parameters:
* `session` - session object
* `id` - session identifier, immutable
## Example
There may be many various options, how the application router extension
decides to store sessions exposed via the session management API. The example
below assumes a `SessionDataAccessObject` to be implemented by the extension
developer and to have the following API:
### Methods:
* `sessionDataAccessObject.create` - `function(session, timeout)`
* `sessionDataAccessObject.update` - `function(sessionId, timeout)`
* `sessionDataAccessObject.delete` - `function(sessionId)`
* `sessionDataAccessObject.load` - `function()`
### Events:
#### `create`
Parameters:
* `sessionId` - session identifier
* `session` - session object serialized to string
* `timeout` - timestamp, when session should expire
* `callback` - function to be called after session is stored in
internal session storage
#### `update`
Parameters:
* `sessionId` - session identifier
* `session` - session object serialized to string
* `timeout` - timestamp, when session should be expired
* `callback` - function to be called after session is stored in
internal session storage
#### `delete`
Parameters:
* `sessionId` - session identifier
#### `load`
Parameters:
* `sessions[]` - array of objects
* `id` - session identifier
* `session` - session object serialized to string
* `timeout` - timestamp, when session should expire
```js
var ar = new require('@sap/approuter')();
var dao = new SessionDataAccessObject();
dao.on('load', function (data) {
ar.start({
getSessionSecret: function getSessionSecret() {
return process.env.SESSION_SECRET;
}
}, function() {
var store = ar.getSessionStore();
var defaultTimeout = store.getDefaultSessionTimeout();
// AppRouter -> Persistence
ar.on('login', function(session) {
dao.create(session, defaultTimeout);
});
ar.on('update', function(sessionId, timeout) {
dao.update(sessionId, timeout);
});
ar.on('logout', function(sessionId) {
dao.delete(sessionId);
});
// Load Initial Data
data.forEach(function(item) {
store.set(item.id, item.session, item.timeout);
});
// Persistence -> AppRouter
dao.on('create', store.set);
dao.on('update', store.set);
dao.on('delete', store.destroy);
});
});
dao.load();
```
## Performance
*__Note:__ The `update` event of the application router may be potentially
triggered thousands of times a second. It is recommended to throttle or
debounce calls to the external storage to reduce network and CPU
consumption.*
Here is an example of a throttled `dao.update()`, where the latest change
will be persisted in the external storage no more than once in `500ms` for
the same session.
```js
// Throttled update
update(sessionId, timeout) {
var dao = this;
var sessionStore = this._sessionStore;
if(typeof timeout === 'undefined') {
if (!this.updateTimers[sessionId]) {
this.updateTimers[sessionId] = setTimeout(function() {
dao.updateTimers[sessionId] = null;
}, 500);
sessionStore.get(sessionId, function(err, session) {
dao._saveSession(sessionId, session)
});
}
} else {
if (!this.timeoutTimers[sessionId]) {
this.timeoutTimers[sessionId] = setTimeout(function() {
dao.timeoutTimers[sessionId] = null;
}, 500);
sessionStore.getSessionTimeout(sessionId, function(err, timeout) {
dao._saveTimeout(sessionId, timeout)
});
}
}
}
```
And here is an example of a debounced `dao.update()`, where the latest
change will be persisted in the external storage only if there were no other
changes during the last `500ms` for the same session.
```js
// Debounced update
update(sessionId, timeout) {
var dao = this;
var sessionStore = this._sessionStore;
if(typeof timeout === 'undefined') {
if (this.updateTimers[sessionId]) {
clearTimeout(this.updateTimers[sessionId]);
}
this.updateTimers[sessionId] = setTimeout(function() {
sessionStore.get(sessionId, function(err, session) {
dao._saveSession(sessionId, session)
});
}, 500);
} else {
if (this.timeoutTimers[sessionId]) {
clearTimeout(this.timeoutTimers[sessionId]);
}
this.timeoutTimers[sessionId] = setTimeout(function() {
sessionStore.getSessionTimeout(sessionId, function(err, timeout) {
dao._saveTimeout(sessionId, timeout)
});
}, 500);
}
}
```
To understand the difference between throttling and debouncing, let's
consider an example, where requests for the same session come every
`100ms` for `1sec`. In case of `500ms` debouncing, changes will be
persisted one time. In case of `500ms` throttling, changes will be
persisted two times. Without any optimisation, changes will be
persisted ten times.
## Custom Storage Driver
It is possible to use your own driver. In order to do that, user shall inject its own implementation of a store.
The class shall implement the following interface:
```typescript
interface UserCustomStore {
// delete all sessions
clear(): Promise<void>;
// remove <sessionId> session
destroy(sessionId : string): Promise<void>;
// retrieve <sessionId> session
get(sessionId : string): Promise<object | null>;
// number of sessions
length(): Promise<number>;
// get <sessionId> expiration
ttl(sessionId : string): Promise<number>;
// set <sessionId> data to <session> with <timeout> expiration
set(sessionId: string, session: object, timeout: number): Promise<void>;
// check if session <sessionId> exists
exists(sessionId: string): boolean;
// update existing session <sessionId> expiration to <timeout>
resetTimer(sessionId: string, timeout: number);
}
```
In addition, the file should include a method to get an instance of the store, for example:
```typescript
let store;
module.exports.getStore = () => {
if (!store) {
store = new UserCustomStore();
}
return store;
};
```
see [Redis store](../lib/utils/redis-store.js) for example
In order for app router to use it, user shall set ```externalStoreFilePath``` property in the ```EXT_SESSION_MGT``` env variable with the path to the storage.
The application router will use this path to require your storage.
The application router uses the defaultRetryTimeout and the backOffMultiplier properties in the EXT_SESSION_MGT environment variable to determine the Redis pattern for automatic retries of failed operations.
For example:
```json
{
"instanceName": "approuter-redis",
"storageType": "redis",
"sessionSecret": "someuniquesessionsecret",
"externalStoreFilePath": "./src/storage/my-special-storage",
"defaultRetryTimeout": 10000,
"backOffMultiplier": 10
}
```
## Credentials Structure when using Redis
Redis store can be used on both CF and Kyma, however the external session managment expects to receive the credentials in a certain structure,
similar to the structure of the CF redis service instance.
If you're using Kyma, create a [K8s secret](https://kubernetes.io/docs/tasks/configmap-secret/managing-secret-using-config-file/#create-the-config-file) that includes the credentials in the following structure:
```json
{
"cluster_mode": "(Mandatory) boolean",
"tls": "(Mandatory) boolean",
"ca_base64": "(Optional) string",
"sentinel_nodes": "(Optional) Array of objects of hostname and port, e.g '[{hostname: 127.0.0.1, port: 26543}]'",
"uri": "(Mandatory if using sentinel_nodes) string",
"password": "(Mandatory) string",
"hostname": "(Mandatory) string",
"port": "string"
}
```

91
app1/node_modules/@sap/approuter/doc/sizingGuide.md generated vendored Normal file
View file

@ -0,0 +1,91 @@
Sizing Guide for Application Router
===================================
<!-- toc -->
- [Idle](#idle)
- [Test Setup](#test-setup)
- [HTTP Traffic](#http-traffic)
- [Web Socket Traffic](#web-socket-traffic)
- [Memory Configuration](#memory-configuration)
<!-- tocstop -->
In this guide we provide measurements done in different application router scenarios. You can use them to approximately calculate the amount of memory that would be required by the application router. The tables contain the exact results from the measurements with Node.js v6.9.1. It is a good idea to provide higher numbers for productive usage.
All measurements are with authentication. If you have additional session content and want to count the session memory consumption please take a look at what is stored in the session - described in README's [Session Contents](../README.md#session-contents) section. You will need to add the calculated session size taking into account the number of different users and the session timeout. In our tests only the JWT token took ~4KB.
## Idle
The memory consumption for an idle application router is around 50 MB.
## Test Setup
The application router runs in a container with limited amount of memory. Swap is turned off.
The test client creates new sessions on the server with a step of 100.
No more than 100 users request the application router at a given time
(e.g. 100 sessions are initialized and become idle, then 100 more session are created and become idle ...).
The test ends when an *Out of Memory* event occurs, causing the container to be stopped.
The number of created sessions before the process ends is taken.
## HTTP Traffic
There are 2 separate test scenarios depending on what is done after a session is created:
- Scenario (1)
- A 'Hello World' static resource is being served.
- Scenario (2)
- A 'Hello World' static resource is being served.
- A static resource of 84.78kb (compressed by application router to 28.36kb) is being served.
- A backend which returns a payload of 80kb (compressed by application router to 58kb) is being called.
- Another backend which returns a payload of 160kb (compressed by application router to 116kb) is being called.
Memory Limit | Max Sessions - Scenario (1) | Max Sessions - Scenario (2)
------------ | --------------------------- | ---------------------------
256MB | 5 300 | 800
512MB | 13 300 | 2 300
1GB | 30 100 | 8 400
2GB | 65 500 | 19 500
4GB | 134 900 | 46 400
8GB | 275 500 | 102 300
## Web Socket Traffic
There are 2 separate test scenarios depending on what is done after a session is created:
- Scenario (1)
- A 'Hello World' static resource is being served.
- A single 'Hello' message is sent and then received through a web socket connection.
- Scenario (2)
- A 'Hello World' static resource is being served.
- A backend which returns a payload of 80kb over a web socket is being called.
- Another backend which returns a payload of 160kb over a web socket is being called.
**Note**: Web sockets require a certain amount of file handles to be available to the process - it is approximately two times the number of the sessions.
In Cloud Foundry the default value is 16384.
Memory Limit | Max Sessions - Scenario (1) | Max Sessions - Scenario (2)
------------ | --------------------------- | ---------------------------
256MB | 600 | 300
512MB | 1 100 | 500
1GB | 3 100 | 800
2GB | 6 500 | 1 400
4GB | 13 300 | 2 900
8GB | 20 700 | 6 100
**Note**: `--max-old-space-size` restricts the amount of memory used in the JavaScript heap.
Its default value is below 2GB. So in order to use the full resources that has been provided to the application,
the value of this restriction should be set to a number equal to the memory limit of the whole application.
For example, if the application memory is limited to 2GB, set the V8 heap limit like this in the `package.json`:
```
"scripts": {
"start": "node --max-old-space-size=2048 node_modules/@sap/approuter/approuter.js"
}
```
## Memory Configuration
Application router process should run with at least 256MB memory. It may require more memory depending on the application.
These aspects influence memory usage:
- concurrent connections
- active sessions
- JWT token size
- backend session cookies

View file

@ -0,0 +1,41 @@
'use strict';
const Cache = require('lru-cache');
const HttpProxyAgent = require('http-proxy-agent');
const HttpsProxyAgent = require('https-proxy-agent');
const HttpKeepAliveAgent = require('agentkeepalive');
const HttpsKeepAliveAgent = HttpKeepAliveAgent.HttpsAgent;
exports.proxyAgentsCache = new Cache(10);
exports.httpAgent = new HttpKeepAliveAgent();
exports.httpsAgent = new HttpsKeepAliveAgent();
function isSecureConnection(protocol) {
return protocol === 'https:' || protocol === 'wss:';
}
exports.get = function (protocol, proxy, connectivityScenario) {
let isSecure = connectivityScenario ? false : isSecureConnection(protocol);
if (proxy) {
return retrieveProxyAgent(proxy, isSecure);
}
return isSecure ? exports.httpsAgent : exports.httpAgent;
};
function retrieveProxyAgent(proxy, isSecure) {
let id = buildIdentifier(proxy, isSecure);
let agent = exports.proxyAgentsCache.get(id);
if (agent) {
return agent;
}
agent = isSecure ? new HttpsProxyAgent(proxy) : new HttpProxyAgent(proxy);
exports.proxyAgentsCache.set(id, agent);
return agent;
}
function buildIdentifier(proxy, isSecure) {
let suffix = isSecure ? 'secure' : 'plain';
return proxy + ':' + suffix;
}

View file

@ -0,0 +1,198 @@
'use strict';
const _ = require('lodash');
const url = require('url');
const headerUtil = require('../utils/header-util');
const pathUtil = require('../utils/path-util');
const cookieUtils = require('../utils/cookie-utils');
const businessServiceUtils = require('../utils/business-service-utils');
const dynamicRoutingUtils = require('../utils/dynamic-routing-utils');
const passportUtils = require('../passport/utils');
let HEADERS_TO_REMOVE = ['connection',
'keep-alive',
'public',
'proxy-authenticate',
'transfer-encoding',
'upgrade',
'x-approuter-authorization',
'x-custom-host',
':authority' ,
':method',
':path',
':scheme',
'sec-websocket-key'];
exports.getHeaders = function (req, accessToken, destination) {
let logger = req && req.loggingContext && req.loggingContext.getLogger && req.loggingContext.getLogger('/headers.js');
let headers;
let route = req && req.internalUrl ? req.internalUrl.route : null;
let tokens = {
// In flow of Logout:
// env destination => we get JWT token in tokens.accessToken as a String
// destination service destination => we get destination auth token in tokens.accessToken as an Object
accessToken: accessToken
};
if (req) {
// 1. Access token returned from destination or bs service logout
tokens.accessToken = tokens.accessToken ||
// 2. Access token obtained from local xsuaa in saas-approuter flow
(req.session && req.session.user && req.destinationCredentials && req.destinationCredentials.uniqueServiceName
&& req.session.user.businessServices && req.session.user.businessServices[req.destinationCredentials.uniqueServiceName]
&& req.session.user.businessServices[req.destinationCredentials.uniqueServiceName].accessToken) ||
// 3. Access token obtained from the statically bound xsuaa instance
(req.session && req.session.user && req.session.user.token && req.session.user.token.accessToken);
destination = destination || req.internalUrl.destination;
tokens.service2ApprouterToken = req.headers['x-approuter-authorization'];
let servicePreserveHostHeader = req.internalUrl && req.internalUrl.route && req.internalUrl.route.service &&
req.internalUrl.route.credentials && req.internalUrl.route.credentials.preserve_host_header;
if ((destination && destination.preserveHostHeader) || servicePreserveHostHeader) {
headers = _.omit(req.headers, HEADERS_TO_REMOVE);
} else {
headers = _.omit(req.headers, HEADERS_TO_REMOVE.concat(['host']));
}
if (destination && destination['sap-client']) {
headers['sap-client'] = destination['sap-client'];
}
if (destination && destination.proxyType === 'OnPremise' && req.app.services && req.app.services['connectivity']) {
tokens['connectivity'] = req.tenant && req.app.services['connectivity'][req.tenant] && req.app.services['connectivity'][req.tenant].token ?
req.app.services['connectivity'][req.tenant].token : req.app.services['connectivity'].token;
}
if (destination && req.session && req.session.user && req.session.user.destinationUserExchangeToken){
tokens['userToken'] = req.session.user.destinationUserExchangeToken.token;
}
if (destination && req.session && req.session.user && req.session.user.destinationKey && req.session.user.destinationKey.destinationUserExchangeToken){
tokens['userToken'] = req.session.user.destinationKey.destinationUserExchangeToken.token;
}
addXForwardingHeaders(headers, req, destination);
removeSecurityHeaders(headers, req);
if (req.headers['x-approuter-authorization'] && headers.cookie) {
const backendName = cookieUtils.getBackendName(req);
headers.cookie = cookieUtils.decryptCookies(headers.cookie, backendName, logger);
}
headerUtil.updateSapPassport(headers);
const destinationKey = req.destinationKey && destination && destination.name ? req.destinationKey + '-' + destination.name : (destination && destination.name);
if (destinationKey && req.session && req.session.user && req.session.user.destinations
&& req.session.user.destinations[destinationKey]
&& req.session.user.destinations[destinationKey].authToken){
tokens['authToken'] = req.session.user.destinations[destinationKey].authToken;
} else if (req.internalUrl && req.internalUrl.route
&& req.internalUrl.route.destination && req.internalUrl.route.authToken){
tokens['authToken'] = req.internalUrl.route.authToken;
}
// technical services
if (route && route.service && req.app && req.app.services && req.app.services[route.service]){
let tenant = req && passportUtils.getUrlTenant(req);
tokens[route.service] = (tenant && req.app.services[route.service][tenant] && req.app.services[route.service][tenant].token) ?
req.app.services[route.service][tenant].token : req.app.services[route.service].token;
}
if (route && route.service === 'html5-apps-repo-rt') {
const appKey = dynamicRoutingUtils.getApplicationKey(req);
const businessServiceName = appKey && ((appKey.bsKey && appKey.bsKey.appPrefix) || appKey.appPrefix);
if (businessServiceName) {
addAppHostIdHeader(businessServiceName, headers, req);
}
}
if (route && route.credentials && businessServiceUtils.forwardIasToken(route.credentials) && isIasLoginToken(req)){
tokens.forwardIasToken = req.session.user.token.accessToken;
}
}
const logInfo = req && passportUtils.getTenantInfo(req).logInfo;
logInfo && !headers.tenantid && (headers.tenantid = logInfo);
return addOauthHeader(headers || {}, tokens, route, destination);
};
function removeSecurityHeaders(headers, req) {
if (!pathUtil.isPublicPath(req)) {
delete headers['authorization'];
if (pathUtil.isCsrfProtectionEnabled(req)) {
delete headers['x-csrf-token'];
}
}
}
function addOauthHeader(headers, tokens, route, destination) {
// Destination flow
if (route && route.destination) {
// On premise destination flow - cloud connector
if (destination.proxyType && destination.proxyType === 'OnPremise') {
if ((tokens.accessToken && !tokens.service2ApprouterToken) ||
(tokens.accessToken && tokens.service2ApprouterToken)) {
if ((!process.env.SEND_CONNECTIVITY_AUTHENTICATION && destination.authentication === 'PrincipalPropagation') ||
(process.env.SEND_CONNECTIVITY_AUTHENTICATION && destination.authentication !== 'NoAuthentication')){
headers['SAP-Connectivity-Authentication'] = 'Bearer ' + (tokens.userToken || tokens.accessToken);
}
}
headers['Proxy-Authorization'] = 'Bearer ' + tokens['connectivity'].accessToken;
if (destination.cloudConnectorLocationId) {
headers['SAP-Connectivity-SCC-Location_ID'] = destination.cloudConnectorLocationId;
}
} else { // Cloud destination flow
if (tokens && tokens.accessToken && destination.forwardAuthToken) {
headers.authorization = 'Bearer ' + tokens.accessToken;
}
}
// Common for on premise and cloud destination flows
// Use tokens provided from destination service
if (tokens['authToken'] && tokens['authToken']['http_header'].value) {
headers[tokens['authToken']['http_header'].key] = tokens['authToken']['http_header'].value;
}
} else if (route && route.service) {// Service flow
if (businessServiceUtils.getGrantType(route.credentials) === 'client_credentials') {// html5 apps repo flow
headers.authorization = tokens[route.service].tokenType + ' ' + tokens[route.service].accessToken;
} else {
headers.authorization = 'Bearer ' + tokens.accessToken;
if (tokens.forwardIasToken){
headers['x-ias-token'] = tokens.forwardIasToken;
}
}
} else {// WS or Logout
if (tokens && tokens.accessToken) {
// In case of Logout:
// env destination => we send JWT token in tokens.accessToken as a String
// destination service destination => we send destination auth token in tokens.accessToken as an Object
let tokenType = tokens.accessToken.type ? tokens.accessToken.type : 'Bearer';
let tokenValue = tokens.accessToken.value ? tokens.accessToken.value : tokens.accessToken;
headers.authorization = tokenType + ' ' + tokenValue;
}
}
return headers;
}
function addXForwardingHeaders(headers, req, destination) {
if (destination && destination.setXForwardedHeaders === false){
const xForwardedHeadersKeys = Object.keys(headers).filter(key => key.startsWith('x-forwarded'));
xForwardedHeadersKeys.forEach(headerKey => delete headers[headerKey]);
return;
}
if (req.headers.host) {
headers['x-forwarded-host'] = req.headers['x-forwarded-host'] || req.headers.host;
}
if (!headers['x-forwarded-proto'] && req.protocol) {
headers['x-forwarded-proto'] = req.headers['x-forwarded-proto'] || req.protocol;
}
if (!headers['x-forwarded-for']) {
headers['x-forwarded-for'] = req.connection.remoteAddress;
}
if (!headers['x-forwarded-path']) {
headers['x-forwarded-path'] = req.headers['x-forwarded-path'] || url.parse(req.url).pathname;
}
}
function addAppHostIdHeader(businessServiceName, headers, req) {
let businessServiceCredentials = businessServiceUtils.getCredentials(businessServiceName, true, req);
if (businessServiceCredentials && businessServiceCredentials['html5-apps-repo'] && businessServiceCredentials['html5-apps-repo']['app_host_id']) {
headers['x-app-host-id'] = businessServiceCredentials['html5-apps-repo']['app_host_id'];
}
}
function isIasLoginToken(req){
return req && req.session && req.session.user && req.session.user.token.authenticationType === 'ias';
}

View file

@ -0,0 +1,336 @@
'use strict';
/*
* express-session
* Copyright(c) 2010 Sencha Inc.
* Copyright(c) 2011 TJ Holowaychuk
* Copyright(c) 2015 Douglas Christopher Wilson
* MIT Licensed
*/
/**
* Module dependencies.
* @private
*/
const session = require('express-session');
const util = require('util');
const tracer = require('../utils/logger').getTracer(__filename);
/**
* Shim setImmediate for node.js < 0.10
* @private
*/
/**
* A session store in memory.
* @param {Object} [options]
* @param {Number} [options.timeout] Session timeout (in minutes). Number has to be positive.
* @public
*/
let Store = session.Store;
function MemoryStore(options) {
Store.call(this);
options = options || {};
this.store = options.externalSessionStore;
this.options = {};
this.sessions = {};
this.sessionTimers = {};
if (options.timeout && options.timeout < 1) {
options.timeout = null;
}
this.options.timeout = (options.timeout || 15) * 6;
let getSessionFn = getSession.bind(this);
let self = this;
this.sessionChecker = setInterval(function () {
for (let sessionId in self.sessionTimers) {
if (--self.sessionTimers[sessionId] <= 0) {
getSessionFn(sessionId, (sessionObject, err) => {
if (!err && sessionObject) {
self.emit('timeout', sessionObject);
self.destroy(sessionId);
}
});
}
}
},
10 * 1000); // runs every 10 seconds
return this;
}
/**
* Inherit from Store.
*
*/
util.inherits(MemoryStore, Store);
/**
* Get all active sessions.
*
* @param {function} callback
* @public
*/
MemoryStore.prototype.all = function all(callback) {
let sessions = {};
Object.entries(this.sessions).forEach(([ sessionId, session ]) => {
const sessionObject = JSON.parse(session);
sessionObject.id = sessionId;
sessions[sessionId] = JSON.stringify(sessionObject);
});
callback && callback(null, sessions);
};
/**
* Clear all sessions.
*
* @param {function} callback
* @public
*/
MemoryStore.prototype.clear = function clear(callback) {
this.store && this.store.clear();
this.sessions = {};
this.sessionTimers = {};
callback && callback();
};
/**
* Destroy the session associated with the given session ID.
*
* @param {string} sessionId
* @public
*/
MemoryStore.prototype.destroy = function destroy(sessionId, callback) {
this.store && this.store.destroy(sessionId);
if (!this.sessions[sessionId]) {
return callback && callback();
}
let session = this.sessions[sessionId];
delete this.sessionTimers[sessionId];
delete this.sessions[sessionId];
this.emit('destroy', JSON.parse(session));
callback && callback();
};
/**
* Fetch session by the given session ID. Never returns error.
*
* @param {string} sessionId
* @param {function} callback
* @public
*/
MemoryStore.prototype.get = function get(sessionId, callback) {
getSessionResetTimeout.call(this, sessionId, session => callback(null, session));
};
/**
* Commit the given session associated with the given sessionId to the store.
*
* @param {string} sessionId
* @param {object} session
* @param {function} callback
* @public
*/
/**
* Get number of active sessions.
*
* @param {function} callback
* @public
*/
MemoryStore.prototype.length = function length(callback) {
callback(null, Object.keys(this.sessions).length);
};
MemoryStore.prototype.set = function set(sessionId, session, callback) {
storeSession.call(this, sessionId, session);
resetTimer.call(this, sessionId);
callback && callback();
};
/**
* Touch the given session object associated with the given session ID.
*
* @param {string} sessionId
* @param {object} session
* @param {function} callback
* @public
*/
MemoryStore.prototype.touch = function touch(sessionId, session, callback) {
getSessionResetTimeout.call(this, sessionId, currentSession => {
if (currentSession) {
// update expiration
currentSession.cookie = session.cookie;
storeSession.call(this, sessionId, currentSession);
}
callback && callback();
});
};
/**
* Reset timer for session
*
* @param {string} sessionId
* @param {function} callback
* @public
*/
MemoryStore.prototype.resetSessionTimer = function (sessionId, callback) {
let error = null;
if (this.sessionTimers[sessionId]) {
resetTimer.call(this, sessionId);
} else {
error = new Error('Could not reset session timer! Session ' + sessionId + ' missing!');
}
return callback && callback(error);
};
/**
* Get session timeout
* @returns {Number} Default session timeout in minutes
* @public
*/
MemoryStore.prototype.getDefaultSessionTimeout = function () {
return this.options.timeout / 6;
};
/**
* Get specific session timer
* @param sessionId
* @param callback callback(err, time), time is in minutes
* @public
*/
MemoryStore.prototype.getSessionTimeout = function (sessionId, callback) {
let timer = this.sessionTimers[sessionId];
callback(timer ? null : new Error('Invalid session'), timer && timer / 6);
};
/**
* Updates the session in the memstore by applying function change to it without changing the session timer.
*
* @param {string} sessionId
* @param {function} change A function that changes the session - function change(session)
* @param {boolean} resetTimer defaults to true
*/
MemoryStore.prototype.update = function (sessionId, change, shouldResetTimer) {
getSession.call(this, sessionId, (session, err) => {
if (err && tracer.isEnabled('Error')) {
tracer.error('Failed to get session with sessionId %s', sessionId);
}
if (!session) {
return change(null);
}
change(session);
storeSession.call(this, sessionId, session);
if (shouldResetTimer !== false) {
resetTimer.call(this, sessionId);
}
});
};
/**
* Get session from the store and reset timeout.
* @param {string} sessionId
* @param callback
* @returns {object} session
* @private
*/
function getSessionResetTimeout(sessionId, callback) {
getSession.call(this, sessionId, (session, err) => {
if (err && tracer.isEnabled('Error')) {
tracer.error('Failed to get session with sessionId %s', sessionId);
}
if (session && !session.externalSessionId) {
resetTimer.call(this, sessionId);
}
callback(session);
});
}
/**
* Gets the session from the remote store
* @param sessionId
*/
async function getSessionFromRemoteStore(sessionId) {
const session = await this.store.get(sessionId);
if (session) {
const sessionObject = JSON.parse(session);
sessionObject.id = sessionId;
return sessionObject;
}
}
/**
* Get session from the store.
* @param {string} sessionId
* @param callback
* @returns {object} session
* @private
*/
function getSession(sessionId, callback) {
const sessionJson = this.sessions[sessionId];
if (sessionJson) {
const session = JSON.parse(sessionJson);
Object.defineProperty(session, 'id', { value: sessionId });
callback(session);
} else if (this.store) {
getSessionFromRemoteStore.call(this, sessionId).then(callback);
} else {
callback();
}
}
async function resetTimerRemoteStore(sessionId) {
const exists = await this.store.exists(sessionId);
if (exists) {
await this.store.resetTimer(sessionId, this.options.timeout / 6);
}
}
/**
* Reset timer for session
* @param {string} sessionId
* @private
*/
function resetTimer(sessionId) {
if (this.store) {
resetTimerRemoteStore.call(this, sessionId);
}
let isUpdate = this.sessionTimers.hasOwnProperty(sessionId);
this.sessionTimers[sessionId] = this.options.timeout;
if (isUpdate) {
this.emit('update', sessionId, this.options.timeout / 6); // in minutes
}
}
function storeSession(sessionId, session) {
const stringSession = JSON.stringify(session, function (key, value) {
return (key === 'id') ? undefined : value; // skip id
});
this.store && this.store.set(sessionId, stringSession, this.options.timeout / 6);
let isUpdate = this.sessions.hasOwnProperty(sessionId);
this.sessions[sessionId] = stringSession;
if (isUpdate) {
this.emit('update', sessionId);
}
}
module.exports = MemoryStore;

View file

@ -0,0 +1,92 @@
'use strict';
const http = require('http');
const https = require('https');
const agents = require('./agents');
const backendHeaders = require('./headers');
const urlUtils = require('../utils/url-utils');
const traceUtil = require('../utils/trace-util');
const stripClientCookies = require('../middleware/strip-client-cookies');
const tough = require('tough-cookie');
const passportUtils = require('../passport/utils');
const _ = require('lodash');
const destinationUtils = require('../utils/destination-utils');
exports.getLogoutRequest = function (req, accessToken, destination, xsAppDestConfig, session) {
let method = xsAppDestConfig.logoutMethod || 'POST';
let logoutUrl = urlUtils.join(destination.url, xsAppDestConfig.logoutPath);
let headers = backendHeaders.getHeaders(req, accessToken, destination);
module.exports.getBackendCookies(req,session, headers, destination);
return getRequestImpl(method, urlUtils.parse(logoutUrl), headers, destination, null,session);
};
exports.getBackendCookies = function(req,session, headers, destination){
if (session && session._cookieJar) {
if (req){
stripClientCookies(req, req.app.get('cookieName'));
}
let jar = _.cloneDeep(session._cookieJar); // see tough-cookie issue #59
tough.CookieJar.deserialize(jar, function (err, cookieJar) {
if (err) {
return;
}
cookieJar.getCookieString(destination.url, function(err, cookies){
if (err) {
return;
}
if (cookies) {
headers.cookie = headers.cookie ? headers.cookie + '; ' + cookies : cookies;
}
});
});
}
};
exports.getRequest = function (req, accessToken) {
let headers = backendHeaders.getHeaders(req, accessToken);
let logger = req.logger;
return getRequestImpl(req.method, req.internalUrl, headers, req.internalUrl.destination, logger, req.session);
};
function getRequestImpl(method, parsedUrl, headers, destination, logger, session) {
let proxyUri = exports.buildProxyUri(destination);
let opts = {
method: method,
protocol: parsedUrl.protocol,
hostname: parsedUrl.hostname,
port: parsedUrl.port,
path: parsedUrl.path,
agent: agents.get(parsedUrl.protocol, proxyUri, destination.proxyType === 'OnPremise'),
rejectUnauthorized: destinationUtils.rejectUnauthorized(destination),
headers: headers
};
passportUtils.getAuthCertificates(session, destination, opts);
if (headers.host) { // So socket does not use host header as target (allows differing host header)
opts['servername'] = parsedUrl.hostname;
}
let useSSL = /^https/i.test(opts.protocol);
if (useSSL && opts.agent.options && opts.agent.options.ca) { // fixes leaking ssl sockets in node
opts.ca = opts.agent.options.ca;
}
opts = destinationUtils.getHttpRequestOptions(destination, opts);
if (logger) {
traceUtil.traceBackendRequest(logger, parsedUrl.href, opts, destination);
}
return useSSL ? https.request.bind(undefined, opts) : http.request.bind(undefined, opts);
}
exports.buildProxyUri = function (destination) {
let proxyHost = destination.proxyHost;
let proxyPort = destination.proxyPort;
if (!proxyHost) {
return;
}
let hasProtocol = /^https?:\/\//i.test(proxyHost);
if (hasProtocol) {
return proxyHost + ':' + proxyPort;
}
return 'http://' + proxyHost + ':' + proxyPort;
};

View file

@ -0,0 +1,98 @@
'use strict';
const tough = require('tough-cookie');
const sessionExt = require('../utils/session-ext');
const cookieUtils = require('../utils/cookie-utils');
exports.processCookies = function (setCookieHeader) {
let sessionCookies = [];
let nonSessionCookies = [];
if (setCookieHeader) {
for (let i = 0; i < setCookieHeader.length; ++i) {
let oCookie = tough.Cookie.parse(setCookieHeader[i]);
if (oCookie && isSessionCookie(oCookie)) {
sessionCookies.push(oCookie);
} else {
nonSessionCookies.push(setCookieHeader[i]);
}
}
}
return {
sessionCookies: sessionCookies,
nonSessionCookies: nonSessionCookies
};
};
exports.storeSessionCookies = function(sessionCookies, req, res) {
const isService2Approuter = req.headers['x-approuter-authorization'];
if (sessionCookies.length === 0) {
return;
}
let logger = req.loggingContext.getLogger('/StoreSessionCookies');
if (isService2Approuter && res) {
logger.info('Sending encrypted backend session cookies to client');
const backendName = cookieUtils.getBackendName(req);
let mergedCookies = mergeCookies(req.headers['cookie'],sessionCookies,backendName,logger);
cookieUtils.setCookie(res, cookieUtils.encryptCookies(mergedCookies, backendName));
return;
}
sessionExt.update(req.session, function (session) {
let cookieJar = session._cookieJar ? tough.CookieJar.deserializeSync(session._cookieJar) : new tough.CookieJar();
sessionCookies.forEach(function (oCookie) {
try {
cookieJar.setCookieSync(oCookie, req.internalUrl.href);
} catch (err) {
logger.warning('Could not set session cookie: ', err.message);
}
});
session._cookieJar = cookieJar.serializeSync();
});
};
function isSessionCookie(oCookie) {
return (!oCookie.expires || oCookie.expires === 'Infinity') && (!oCookie.maxAge || oCookie.maxAge === 'Infinity');
}
function mergeCookies(clientCookies, backendCookies, backendName, logger) {
if (!clientCookies || process.env.SERVICE_2_APPROUTER_DISABLE_COOKIE_MERGE) {
return backendCookies;
}
let decryptedClientCookies = cookieUtils.decryptCookies(clientCookies, backendName, logger).split(';');
decryptedClientCookies.forEach(function (clientCookie) {
let equalIndex = clientCookie.indexOf('=');
if (equalIndex > -1) {
let clientCookieKey = clientCookie.substr(0, equalIndex);
let clientCookieValue = clientCookie.substr(equalIndex + 1);
let backendCookieIndex = null;
for (let i = 0; i < backendCookies.length; i++) {
if (backendCookies[i].key) {
if (backendCookies[i].key.trim() === clientCookieKey.trim()) {
backendCookieIndex = i;
break;
}
} else {
logger && logger.error(`Missing key for cookie ${JSON.stringify(backendCookies[i])}`);
}
}
let shouldDelete = shouldDeleteBackendCookie(backendCookies, backendCookieIndex, clientCookieValue, logger);
if (backendCookieIndex === null) {
backendCookies.push(tough.Cookie.parse(`${clientCookieKey}=${clientCookieValue};`));
} else if (shouldDelete) {
backendCookies.splice(backendCookieIndex, 1);
}
}
});
return backendCookies;
}
function shouldDeleteBackendCookie(backendCookies, backendCookieIndex,clientCookieValue, logger){
if (!backendCookies || backendCookies.length === 0 || backendCookieIndex === null){
return false;
}
if (backendCookies[backendCookieIndex].maxAge === 0){
logger.info('Backend cookie ' + backendCookies[backendCookieIndex].key + 'with value ' + backendCookies[backendCookieIndex].value + ' will be deleted');
return true;
} else {
return false;
}
}

171
app1/node_modules/@sap/approuter/lib/bootstrap.js generated vendored Normal file
View file

@ -0,0 +1,171 @@
'use strict';
const https = require('https');
const expressSession = require('express-session');
const passport = require('passport');
const xsenv = require('@sap/xsenv');
const tokenUtils = require('./utils/token-utils');
const bodyParser = require('body-parser');
const cfNodeJsLogging = require('cf-nodejs-logging-support');
const additionalHeadersMiddleware = require('./middleware/additional-headers-middleware');
const agents = require('./backend-request/agents');
const authorizationMiddleware = require('./middleware/authorization-middleware');
const cannotProcessRequestMiddleware = require('./middleware/cannot-process-request-middleware');
const compressionMiddleware = require('./middleware/compression-middleware');
const configuration = require('./configuration');
const connect = require('./connect/connect');
const cookieUtils = require('./utils/cookie-utils');
const connectUtilsMiddleware = require('./middleware/connect-utils-middleware');
const errorHandler = require('./middleware/error-handler');
const extensions = require('./extensions/extensions');
const httpMethodMatchingMiddleware = require('./middleware/http-method-matching-middleware');
const jwtRefreshMiddleware = require('./middleware/jwt-refresh-middleware');
const logging = require('./utils/logger');
const loginCallbackMiddleware = require('./middleware/login-callback-middleware');
const loginMiddleware = require('./middleware/login-middleware');
const logoutMiddleware = require('./middleware/logout-middleware');
const memstoreConfig = require('./configuration/memstore-config');
const oauthConfig = require('./passport/oauth-configuration');
const pathRewritingMiddleware = require('./middleware/path-rewriting-middleware');
const clientCredentialsTokenMiddleware = require('./middleware/client-credentials-token-middleware');
const pluginEndpointMiddleware = require('./middleware/plugin-middleware');
const requestHandler = require('./middleware/request-handler');
const secureCookieMiddleware = require('./middleware/secure-cookie-middleware');
const sessionCookieMiddleware = require('./middleware/session-cookie-middleware');
const staticResourceHandler = require('./middleware/static-resource-handler');
const routeResponseHeadersHandler = require('./middleware/route-response-headers-handler');
const traceRequestMiddleware = require('./middleware/trace-request-middleware');
const welcomePageMiddleware = require('./middleware/welcome-page-middleware');
const whitelistServiceMiddleware = require('./middleware/whitelist-service-middleware');
const xsrfTokenMiddleware = require('./middleware/xsrf-token-middleware');
const attachRouterConfig = require('./middleware/attach-router-config');
const corsMiddleware = require('./middleware/cors-middleware');
const serviceTokenMiddleware = require('./middleware/service-token-middleware');
const destinationTokenMiddleware = require('./middleware/destination-token-middleware');
const subscriptionMiddleware = require('./middleware/subscription-middleware');
const cacheRequestMiddleware = require('./middleware/cache-request-middleware');
const service2ApprouterMiddleware = require('./middleware/service-to-approuter-middleware');
const subscriptionUtils = require('./utils/subscription-utils');
const serviceDestinationsMiddleware = require('./middleware/service-destinations-middleware');
const userApiMiddleware = require('./middleware/user-api-middleware');
const attachZoneInfo = require('./middleware/attach-zone-info');
const sapStatisticsMiddleware = require('./middleware/sap-statistics-middleware');
const applicationLogUtils = require('./utils/application-logs-utils');
const stateParameterMiddleware = require('./middleware/state-parameter-middleware');
const { STATE_PARAMETER_PATH } = require('./passport/utils');
module.exports = function bootstrap(options) {
const certificates = xsenv.loadCertificates();
agents.httpsAgent.options.ca = certificates;
https.globalAgent.options.ca = certificates;
const ext = extensions(options.extensions);
const routerConfig = configuration.load(options);
const app = connect();
// can be overridden by per-request routerConfig
app.set('mainRouterConfig', routerConfig);
// Force logger to run the connect version. (default is express, forcing express is also legal)
cfNodeJsLogging.forceLogger('connect');
// Set the minimum logging level
cfNodeJsLogging.setLoggingLevel(routerConfig.cfNodeJsLoggingLevel);
app.use(sapStatisticsMiddleware);
app.use(logging.getExpressMiddleware());
app.use(connectUtilsMiddleware(app));
app.use(cfNodeJsLogging.logNetwork);
app.use(traceRequestMiddleware);
app.use(STATE_PARAMETER_PATH,bodyParser.json());
app.use(STATE_PARAMETER_PATH,stateParameterMiddleware);
app.use(subscriptionUtils.getSaaSRegistryCallbackPath().onSubscriptionPrefix, bodyParser.json());
app.use(subscriptionUtils.getSMSCallbackPath().onSubscriptionPrefix, bodyParser.json());
app.use(serviceDestinationsMiddleware);
ext.insertMiddleware('first', app);
app.use(attachRouterConfig);
app.use(attachZoneInfo);
app.use(subscriptionMiddleware);
app.use(corsMiddleware);
const externalSessionStore = memstoreConfig.getExternalSessionStore(routerConfig.extSessionMgt);
const sessionSecret = options.getSessionSecret ? options.getSessionSecret() : (cookieUtils.getExternalStoreSessionSecret(routerConfig.extSessionMgt) || cookieUtils.generateSessionSecret());
app.set('sessionCookieKey', sessionSecret);
app.set('externalSessionStore', externalSessionStore);
tokenUtils.getTokens(app);
applicationLogUtils.cacheDynamicLogLevel(app);
const memoryStore = memstoreConfig.getMemoryStore(app);
app.set('memoryStore', memoryStore);
const cookieName = cookieUtils.getSessionCookieName();
app.set('cookieName', cookieName);
app.use(additionalHeadersMiddleware);
app.use(welcomePageMiddleware);
app.use(pathRewritingMiddleware);
app.use(clientCredentialsTokenMiddleware);
app.use(service2ApprouterMiddleware);
const cookies = { secure: routerConfig.secureSessionCookie };
if (routerConfig.cookies && routerConfig.cookies['SameSite']){
cookies.sameSite = routerConfig.cookies['SameSite'];
}
app.use(expressSession({
name: cookieName,
resave: false,
saveUninitialized: false,
secret: sessionSecret,
store: memoryStore,
cookie: cookies,
proxy: true
}));
passport.serializeUser(oauthConfig.getUserSerializer);
passport.deserializeUser(oauthConfig.getUserDeserializer);
app.use(passport.initialize());
app.use(passport.session());
if (routerConfig.secureSessionCookie === true) {
app.use(secureCookieMiddleware);
}
app.use(loginCallbackMiddleware);
app.use(loginMiddleware);
if (routerConfig.jwtRefresh !== 0) {
app.use(jwtRefreshMiddleware);
}
app.use(authorizationMiddleware);
app.use(xsrfTokenMiddleware);
app.use(userApiMiddleware);
app.use(serviceTokenMiddleware);
app.use(destinationTokenMiddleware);
app.use(cacheRequestMiddleware);
const pluginMetadataEndpoint = routerConfig.appConfig.pluginMetadataEndpoint;
pluginMetadataEndpoint && app.use(pluginMetadataEndpoint, pluginEndpointMiddleware);
app.use(logoutMiddleware);
const whitelistService = routerConfig.appConfig.whitelistService;
whitelistService && app.use(whitelistService.endpoint, whitelistServiceMiddleware);
const compressionOptions = routerConfig.appConfig.compression;
compressionOptions.enabled && app.use(compressionMiddleware(compressionOptions));
ext.insertMiddleware('beforeRequestHandler', app);
app.use(httpMethodMatchingMiddleware);
app.use(routeResponseHeadersHandler);
app.use(staticResourceHandler);
app.use(sessionCookieMiddleware(cookieName));
app.use(requestHandler);
ext.insertMiddleware('beforeErrorHandler', app);
app.use(cannotProcessRequestMiddleware);
app.use(errorHandler);
return app;
};

104
app1/node_modules/@sap/approuter/lib/configuration.js generated vendored Normal file
View file

@ -0,0 +1,104 @@
'use strict';
const _ = require('lodash');
const applicationConfig = require('./configuration/app-config');
const configurationUtils = require('./utils/configuration-utils');
const environment = require('./environment');
const environmentConfig = require('./configuration/env-config');
const uaaConfiguration = require('./configuration/uaa-config');
const iasConfiguration = require('./configuration/ias-config');
const validators = require('./configuration/validators');
module.exports.load = function(options) {
let workingDir = environment.getWorkingDirectory(options.workingDir, options.xsappConfig);
let envConfig = environmentConfig.load(workingDir);
let uaaConfig = uaaConfiguration.load(workingDir);
let iasConfig = iasConfiguration.load(workingDir);
let routerConfig = {
appConfig: applicationConfig.loadConfiguration(workingDir, options.xsappConfig || 'xs-app.json',
envConfig.destinations, uaaConfig.xsappname),
serverPort: environment.getPort(options.port),
workingDir: workingDir,
getToken: options.getToken,
getRouterConfig: options.getRouterConfig,
uaaConfig: {
options: uaaConfig
},
iasConfig: {
options: iasConfig
},
httpsOptions: options.httpsOptions
};
return exports.createRouterConfig(routerConfig, envConfig);
};
module.exports.createRouterConfig = function(routerConfig, envConfig) {
mergeConfiguration(routerConfig, envConfig);
configurationUtils.mergePluginsIntoRoutes(routerConfig.appConfig, routerConfig.plugins);
// should be after plugins are merged with xs-app.json provided routes
validateUaaConfiguration(routerConfig);
validateIasConfiguration(routerConfig);
return routerConfig;
};
function mergeConfiguration(routerConfig, envConfig) {
routerConfig.sessionTimeout = envConfig.sessionTimeout || routerConfig.appConfig.sessionTimeout;
delete routerConfig.appConfig.sessionTimeout;
routerConfig.appConfig.compression = routerConfig.appConfig.compression || {};
_.merge(routerConfig.appConfig.compression, envConfig.compression);
routerConfig.uaaConfig.tenantHostPattern = envConfig.tenantHostPattern;
routerConfig.iasConfig.tenantHostPattern = envConfig.tenantHostPattern;
envConfig = _.omit(envConfig, ['sessionTimeout', 'compression', 'tenantHostPattern']);
routerConfig = _.merge(routerConfig, envConfig);
return routerConfig;
}
function validateUaaConfiguration(routerConfig) {
if (!isUaaCheckRequired(routerConfig.appConfig)) {
return;
}
let uaaConfig = routerConfig.uaaConfig;
if (Object.keys(uaaConfig.options).length === 0) {
throw new Error('No UAA service found');
}
validators.validateUaaOptions(uaaConfig.options);
if (uaaConfig.options.tenantmode === 'shared' && !uaaConfig.tenantHostPattern) {
throw new Error('UAA tenant mode is shared, but environment variable TENANT_HOST_PATTERN is not set');
}
}
function isUaaCheckRequired(appConfig) {
return appConfig.authenticationMethod !== 'none' &&
appConfig.routes.some(function(route) {
return route.authenticationType === 'xsuaa';
});
}
function validateIasConfiguration(routerConfig) {
if (!isIasCheckRequired(routerConfig.appConfig)) {
return;
}
let iasConfig = routerConfig.iasConfig;
if (Object.keys(iasConfig.options).length === 0) {
throw new Error('No IAS service found');
}
validators.validateIasOptions(iasConfig.options);
}
function isIasCheckRequired(appConfig) {
return appConfig.authenticationMethod !== 'none' &&
appConfig.routes.some(function(route) {
return route.authenticationType === 'ias';
});
}

View file

@ -0,0 +1,159 @@
'use strict';
const _ = require('lodash');
const fs = require('fs');
const path = require('path');
const VError = require('verror').VError;
const safeRegex = require('safe-regex');
const xsenv = require('@sap/xsenv');
const loggingUtils = require('../utils/logger');
const tracer = loggingUtils.getTracer(__filename);
const logger = loggingUtils.getLogger('/Configuration');
const fsUtils = require('../utils/fs-utils');
const validators = require('./validators');
const prettyPrint = require('../utils/pretty-print');
const configurationUtils = require('../utils/configuration-utils');
const dynamicRoutingUtils = require('../utils/dynamic-routing-utils');
exports.loadConfiguration = function (directory, source, envDestinations, appName) {
let configuration = _.isObject(source) ? source : loadXsAppFromFile(directory, source);
validators.validateXsApp(configuration, envDestinations, directory);
if (configuration.authenticationMethod === 'none') {
logger.warning('No authentication will be used when accessing backends. Scopes defined in routes will be ignored.');
}
configuration.routes = configuration.routes || [];
let hasLocalDirSet = configuration.routes.some(function (currentRoute) {
return !!currentRoute.localDir;
});
if (!hasLocalDirSet) {
if (fsUtils.isDirectory(path.join(directory, 'resources'))) {
configuration.routes.push({
source: '^/(.*)',
localDir: 'resources'
});
} else {
if (!dynamicRoutingUtils.isDynamicRouting()) {
logger.info('xs-app.json: Application does not have directory for static resources!');
}
}
}
processRoutes(configuration, appName);
processCors(configuration);
processCompressResponseMixedTypeContent(configuration);
handleReplace(configuration, directory);
configuration.errorPage = generateErrorPageMap(configuration);
return configuration;
};
function loadXsAppFromFile(directory, source) {
let fullFileName = path.join(directory, source);
try {
tracer.info('Loading configuration from ', fullFileName);
return JSON.parse(fs.readFileSync(fullFileName, 'utf8'));
} catch (e) {
throw new VError(e, 'Invalid content in %s', fullFileName);
}
}
function generateErrorPageMap(configuration) {
return (configuration.errorPage || []).reduce(function (result, errorRoute) {
let arr = Array.isArray(errorRoute.status) ? errorRoute.status : [errorRoute.status];
arr.forEach(function (status) {
result.set(status, errorRoute.file);
});
return result;
}, new Map());
}
function handleReplace(configuration, workingDir) {
let pathToDefaultServices = path.join(workingDir, 'default-services.json');
configuration.routes.forEach(function (route) {
if (route.replace) {
let varsMap = {};
if (route.replace.vars) {
varsMap = route.replace.vars.reduce(function (result, varName) {
result[varName] = process.env[varName];
return result;
}, varsMap);
}
let services = route.replace.services ?
xsenv.getServices(route.replace.services, pathToDefaultServices) : {};
let common = _.intersection(Object.keys(varsMap), Object.keys(services));
if (common.length > 0) {
throw new VError('Route has invalid replace object - colliding services and vars: %s', common);
}
route.replace.view = _.extend(varsMap, services);
}
});
}
function processRoutes(configuration, appName) {
if (!appName) {
logger.info("Replacing $XSAPPNAME will not take place - 'xsappname' property not found in UAA configuration.");
}
configuration.routes.forEach(function (currentRoute) {
currentRoute.source = configurationUtils.constructRegExp(currentRoute.source);
if (!safeRegex(currentRoute.source)) {
logger.warning('Route with source ', currentRoute.source, ' is vulnerable to ReDoS attacks');
}
if (currentRoute.authenticationType === 'none' && currentRoute.scope) {
logger.warning('Route with source ', currentRoute.source, ' does not require authentication. Defined scopes will be ignored.');
}
currentRoute.scope = processScope(currentRoute.scope, appName);
});
if (tracer.isEnabled('debug')) {
tracer.debug('Routes after being processed', JSON.stringify(configuration.routes, prettyPrint.prettyPrintRegExp, 2));
}
}
function processCors(configuration) {
if (!configuration.cors) {
return [];
}
validators.validateCors(configuration.cors);
configuration.cors.forEach(function (currentCors) {
currentCors.uriPattern = configurationUtils.constructRegExp(currentCors.uriPattern);
if (!safeRegex(currentCors.uriPattern)) {
logger.warning('Cors configuration with uriPattern ', currentCors.uriPattern, ' is vulnerable to ReDoS attacks');
}
});
if (tracer.isEnabled('debug')) {
tracer.debug('Cors after being processed', JSON.stringify(configuration.cors, prettyPrint.prettyPrintRegExp, 2));
}
}
function processCompressResponseMixedTypeContent(configuration) {
if (!configuration.compression || (configuration.compression && !configuration.compression.compressResponseMixedTypeContent)) {
configuration.compression.compressResponseMixedTypeContent = false;
return;
}
validators.validateType(configuration.compression.compressResponseMixedTypeContent,'compressResponseMixedTypeContent', 'boolean');
}
function processScope(scope, appName) {
if (_.isString(scope) || Array.isArray(scope)) {
return replaceApplicationName(scope, appName);
}
for (let httpMethod in scope) {
scope[httpMethod] = replaceApplicationName(scope[httpMethod], appName);
}
return scope;
}
function replaceApplicationName(scope, appName) {
scope = _.isString(scope) ? [scope] : scope;
if (!appName) {
return scope;
}
return scope.map(function (scopeName) {
return scopeName.replace(/^\$XSAPPNAME/, appName);
});
}

View file

@ -0,0 +1,213 @@
'use strict';
const _ = require('lodash');
const logger = require('../utils/logger').getLogger('/Configuration');
const path = require('path');
const safeRegex = require('safe-regex');
const VError = require('verror').VError;
const xsenv = require('@sap/xsenv');
const configurationUtils = require('../utils/configuration-utils');
const validators = require('./validators');
const destinationUtils = require('../utils/destination-utils');
exports.load = load;
exports.loadDestinations = loadDestinations;
exports.compression = loadGroupedConfigurations().compression;
function load(workingDir) {
const filePath = path.join(workingDir, 'default-env.json');
xsenv.loadEnv(filePath);
let envConfig = {};
envConfig.destinations = loadDestinations();
envConfig.additionalHeaders = loadAdditionalHeaders();
envConfig.plugins = loadPlugins(envConfig.destinations);
envConfig.clickjackCheckWhitelist = loadClickjackWhitelist();
envConfig.wsAllowedOrigins = loadWebSocketWhitelist();
envConfig.secureSessionCookie = resolveSessionCookieSecureMode();
envConfig.cors = loadCors();
envConfig.cookies = loadCookies();
envConfig.extSessionMgt = loadExternalSessionManager();
const grouped = loadGroupedConfigurations();
envConfig = _.merge(envConfig, grouped);
return envConfig;
}
function loadExternalSessionManager() {
const extSessionMgt = loadJsonVar('EXT_SESSION_MGT');
if (!extSessionMgt) {
return null;
}
validators.validateSessionStoreInstance(extSessionMgt);
return extSessionMgt;
}
function loadCookies() {
const cookies = loadJsonVar('COOKIES');
if (!cookies) {
logger.info('No COOKIES environment variable');
return null;
}
validators.validateCookies(cookies);
return cookies;
}
function loadCors() {
const cors = loadJsonVar('CORS');
if (!cors) {
return;
}
validators.validateCors(cors);
cors.forEach(function (corsConfig) {
corsConfig.uriPattern = configurationUtils.constructRegExp(corsConfig.uriPattern);
if (!safeRegex(corsConfig.uriPattern)) {
logger.warning('Cors configuration with uriPattern ', corsConfig.uriPattern, ' is vulnerable to ReDoS attacks');
}
});
return cors;
}
function loadDestinations(destinations) {
destinations = destinations || loadJsonVar('destinations');
if (!destinations) {
destinations = {};
logger.info('Using empty destinations to run');
return destinations;
}
destinationUtils.normalizeDestinationProperties(destinations);
validators.validateDestinations(destinations);
destinationUtils.adjustDestinationProperties(destinations);
return _.keyBy(destinations, 'name');
}
function loadAdditionalHeaders() {
let additionalHeaders = loadJsonVar('httpHeaders');
if (!additionalHeaders) {
return [];
}
validators.validateHeaders(additionalHeaders);
additionalHeaders = _.map(additionalHeaders, function (header) {
const normalizedHeader = {};
_.forEach(header, function (value, key) {
normalizedHeader[key.toLowerCase()] = value;
});
return normalizedHeader;
});
return additionalHeaders;
}
function loadPlugins(destinations) {
const plugins = loadJsonVar('plugins');
if (!plugins) {
return null;
}
validators.validatePlugins(plugins, destinations);
plugins.forEach(function (currentPlugin) {
currentPlugin.source = configurationUtils.constructRegExp(currentPlugin.source);
});
return plugins;
}
function loadClickjackWhitelist() {
return loadOriginsWhitelist('CJ_PROTECT_WHITELIST');
}
function loadWebSocketWhitelist() {
return loadOriginsWhitelist('WS_ALLOWED_ORIGINS');
}
function loadOriginsWhitelist(envVar) {
const whitelist = loadJsonVar(envVar);
if (!whitelist) {
return null;
}
validators.validateWhitelist(whitelist);
whitelist.forEach(function (item) {
if (typeof item.port === 'string') {
item.port = parseInt(item.port);
}
});
return whitelist;
}
function resolveSessionCookieSecureMode() {
switch (process.env.SECURE_SESSION_COOKIE) {
case 'true':
return true;
case 'false':
return false;
default:
return 'auto';
}
}
function loadGroupedConfigurations() {
let grouped = {
sessionTimeout: loadJsonVar('SESSION_TIMEOUT'),
incomingConnectionTimeout: loadJsonVar('INCOMING_CONNECTION_TIMEOUT'),
jwtRefresh: loadJsonVar('JWT_REFRESH'),
tenantHostPattern: process.env['TENANT_HOST_PATTERN'],
destinationHostPattern: process.env['DESTINATION_HOST_PATTERN'],
compression: loadJsonVar('COMPRESSION'),
externalReverseProxy: loadJsonVar('EXTERNAL_REVERSE_PROXY'),
directRoutingUriPatterns: loadJsonVar('DIRECT_ROUTING_URI_PATTERNS'),
cfNodeJsLoggingLevel: process.env['CF_NODEJS_LOGGING_LEVEL'] || 'error',
http2Support: loadJsonVar('HTTP2_SUPPORT'),
serverKeepAlive: loadJsonVar('SERVER_KEEP_ALIVE'),
minimumTokenValidity: loadJsonVar('MINIMUM_TOKEN_VALIDITY'),
dynamicIdentityProvider: loadJsonVar('DYNAMIC_IDENTITY_PROVIDER'),
stateParameterSecret: process.env['STATE_PARAMETER_SECRET'],
enableXForwardHostValidation: loadJsonVar('ENABLE_X_FORWARDED_HOST_VALIDATION'),
enableCSPheaders: loadJsonVar('ENABLE_FRAME_ANCESTORS_CSP_HEADERS')
};
grouped = _.pickBy(grouped, function (value) {
return (value || value === '' || value === 0);
});
if (!grouped.hasOwnProperty('jwtRefresh')) {
grouped.jwtRefresh = 5; // 5 is the default JWT_REFRESH
}
const sendXFrameOptions = loadJsonVar('SEND_XFRAMEOPTIONS');
grouped.sendXFrameOptions = (typeof sendXFrameOptions !== 'undefined') ? sendXFrameOptions : true;
validators.validateEnvironmentSettings(grouped);
if (grouped.tenantHostPattern) {
grouped.tenantHostPattern = configurationUtils.constructRegExp(grouped.tenantHostPattern);
if (!safeRegex(grouped.tenantHostPattern)) {
logger.warning('TENANT_HOST_PATTERN is vulnerable to ReDoS attacks');
}
}
if (grouped.destinationHostPattern) {
grouped.destinationHostPattern = configurationUtils.constructRegExp(grouped.destinationHostPattern);
if (!safeRegex(grouped.destinationHostPattern)) {
logger.warning('DESTINATION_HOST_PATTERN is vulnerable to ReDoS attacks');
}
}
if (grouped.directRoutingUriPatterns) {
grouped.directRoutingUriPatterns.forEach(function (currentDirectPattern) {
if (validators.checkStringOrRegex(currentDirectPattern)) {
return;
}
throw new VError('Direct routing uri pattern %s must be a string or a regular expression', currentDirectPattern.toString());
});
}
return grouped;
}
function loadJsonVar(envVar) {
if (envVar in process.env) {
try {
return JSON.parse(process.env[envVar]);
} catch (e) {
throw new VError(e, 'Invalid value for environment variable %s', envVar);
}
}
}

View file

@ -0,0 +1,39 @@
'use strict';
const _ = require('lodash');
const path = require('path');
const xsenv = require('@sap/xsenv');
const VError = require('verror').VError;
const tracer = require('../utils/logger').getTracer(__filename);
exports.load = function (workingDir) {
let pathToDefaultServices = path.join(workingDir, 'default-services.json');
try {
let iasCredentials = xsenv.getServices({ ias: matchesIas }, pathToDefaultServices).ias;
if (process.env.IAS_PRIVATE_KEY){
iasCredentials.key = process.env.IAS_PRIVATE_KEY;
}
if (iasCredentials.domains && !iasCredentials.domain) {
iasCredentials.domain = typeof(iasCredentials.domains) === 'string' ? JSON.parse(iasCredentials.domains)[0] : iasCredentials.domains[0];
}
return iasCredentials;
} catch (e) {
tracer.debug(new VError(e, 'Cannot find service ias in environment'));
return {};
}
};
function matchesIas(service) {
let iasName = process.env.IAS_SERVICE_NAME;
if (iasName) {
return service.name === iasName;
}
if (_.includes(service.label, 'identity') || service.label === 'identity') {
return true;
}
if (service.label === 'user-provided' && service.credentials && (_.includes(service.credentials.label, 'identity') || service.label === 'identity')) {
return true;
}
return false;
}

View file

@ -0,0 +1,28 @@
'use strict';
const MemoryStore = require('../backend-request/memstore');
const logoutProvider = require('../middleware/logout-provider');
exports.getExternalSessionStore = function (extSessionMgt) {
if (extSessionMgt) {
const filePath = extSessionMgt.externalStoreFilePath || `../utils/${extSessionMgt.storageType}-store`;
return require(filePath).getStore(extSessionMgt.instanceName);
}
};
exports.getMemoryStore = function (app) {
const memoryStore = new MemoryStore({
timeout: app.get('mainRouterConfig').sessionTimeout,
externalSessionStore: app.get('externalSessionStore')
});
memoryStore.on('timeout', function (session) {
logoutProvider.sessionTimeoutLogout(session, app);
});
memoryStore.on('update', function (sessionId, timeout) {
app.approuter.emit('update', sessionId, timeout);
});
return memoryStore;
};

View file

@ -0,0 +1,36 @@
{
"type": "object",
"additionalProperties": true,
"properties": {
"instanceid": {
"type": "string",
"minLength": 1
},
"uaa": {
"type": "object",
"properties": {
"clientid": {
"type": "string",
"minLength": 1
},
"url": {
"type": "string",
"minLength": 1,
"format": "absolute-uri"
}
},
"required": [
"clientid",
"url"
]
},
"grant_type": {
"type": "string",
"enum": [
"user_token", "client_credentials"
]
}
},
"required": ["uaa"]
}

View file

@ -0,0 +1,11 @@
{
"type": "object",
"additionalProperties": true,
"properties": {
"clientid": { "type": "string", "minLength": 1 },
"url": { "type": "string", "minLength": 1, "format": "absolute-uri" },
"onpremise_proxy_host": { "type": "string", "minLength": 1 },
"onpremise_proxy_port": { "type": "string", "minLength": 1 }
},
"required": ["clientid", "url", "onpremise_proxy_host", "onpremise_proxy_port"]
}

View file

@ -0,0 +1,7 @@
{
"type": "object",
"additionalProperties": false,
"properties": {
"SameSite": { "type": "string", "enum": ["None", "Lax"]}
}
}

View file

@ -0,0 +1,5 @@
{
"id" : "Destinations",
"type": "array",
"format": "no-duplicate-names"
}

View file

@ -0,0 +1,19 @@
{
"type": "object",
"additionalProperties": false,
"properties": {
"name": { "type": "string", "minLength": 1 },
"url": { "type": "string", "minLength": 1, "format": "absolute-uri" },
"proxyHost": { "type": "string", "minLength": 1 },
"proxyPort": { "type": ["string", "integer"], "minLength": 1, "format": "valid-port" },
"forwardAuthToken": { "type": "boolean" },
"forwardAuthCertificates": { "type": "boolean" },
"IASDependencyName": { "type": "string", "minLength": 1 },
"strictSSL": { "type": "boolean" },
"timeout": { "type": "integer", "minimum": 1 },
"proxyType": { "type": "string", "enum": ["OnPremise"] },
"setXForwardedHeaders": {"type": "boolean"}
},
"required": ["name", "url"],
"dependencies": { "proxyHost": ["proxyPort"], "proxyPort": ["proxyHost"] }
}

View file

@ -0,0 +1,31 @@
{
"type": "object",
"additionalProperties": false,
"properties": {
"sessionTimeout": { "type": "integer", "minimum": 1 },
"serverKeepAlive": { "type": "integer", "minimum": 0 },
"jwtRefresh": { "type": "integer", "minimum": 0 },
"incomingConnectionTimeout": { "type": "integer", "minimum": 0 },
"minimumTokenValidity": { "type": "integer", "minimum": 0 },
"sendXFrameOptions": { "type": "boolean" },
"tenantHostPattern": { "type": "string", "minLength": 1, "format": "regexWithCapture" },
"destinationHostPattern": { "type": "string", "minLength": 1, "format": "regexWithCapture" },
"externalReverseProxy": { "type": "boolean"},
"http2Support": { "type": "boolean"},
"directRoutingUriPatterns": {"type": "array", "items": {"type": "string", "minLength": 1}, "minItems": 1},
"compression": {
"type": "object",
"additionalProperties": false,
"properties": {
"enabled": { "type": "boolean" },
"minSize": { "type": "integer", "minimum": 1 },
"compressResponseMixedTypeContent": { "type": "boolean" }
}
},
"cfNodeJsLoggingLevel": { "type": "string", "minLength": 1 },
"dynamicIdentityProvider": { "type": "boolean"},
"stateParameterSecret": { "type": "string", "minLength": 1 },
"enableXForwardHostValidation": { "type": "boolean"},
"enableCSPheaders": { "type": "boolean"}
}
}

View file

@ -0,0 +1,35 @@
{
"type": "object",
"additionalProperties": false,
"properties": {
"instanceName": {
"type": "string",
"minLength": 1
},
"storageType": {
"type": "string",
"minLength": 1
},
"sessionSecret": {
"type": "string",
"minLength": 1
},
"externalStoreFilePath": {
"type": "string",
"minLength": 1
},
"defaultRetryTimeout": {
"type": "integer",
"minimum": 1
},
"backOffMultiplier": {
"type": "integer",
"minimum": 1
}
},
"required": [
"instanceName",
"storageType",
"sessionSecret"
]
}

View file

@ -0,0 +1,10 @@
{
"type": "array",
"items": {
"type": "object",
"maxProperties": 1,
"minProperties": 1,
"format": "headers-rules",
"additionalProperties": { "type": "string", "minLength": 1, "format": "valid-header-value" }
}
}

View file

@ -0,0 +1,25 @@
{
"type": "object",
"additionalProperties": true,
"properties": {
"instanceid": { "type": "string", "minLength": 1 },
"uaa": {
"type": "object",
"properties": {
"clientid": {
"type": "string",
"minLength": 1
},
"url": {
"type": "string",
"minLength": 1,
"format": "absolute-uri"
}
},
"required": ["clientid", "url"]
},
"grant_type": {"type": "string", "enum": ["client_credentials"]},
"uri": { "type": "string", "minLength": 1, "format": "absolute-uri" }
},
"required": ["uri","grant_type"]
}

View file

@ -0,0 +1,9 @@
{
"type": "object",
"additionalProperties": true,
"properties": {
"clientid": { "type": "string", "minLength": 1 },
"url": { "type": "string", "minLength": 1, "format": "absolute-uri" }
},
"required": ["clientid","url"]
}

View file

@ -0,0 +1,52 @@
{
"type": "object",
"additionalProperties": false,
"properties": {
"getToken": { "format": "function" },
"getSessionSecret": { "format": "function" },
"getRouterConfig": { "format": "function" },
"workingDir": { "type": "string", "minLength": 1 },
"port": { "type": ["string", "integer"], "minLength": 1, "format": "valid-port" },
"httpsOptions": {
"type" : ["object"]
},
"extensions": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"properties": {
"insertMiddleware": {
"type": "object",
"additionalProperties": false,
"properties": {
"first": { "$ref": "#/definitions/middlewareList" },
"beforeRequestHandler": { "$ref": "#/definitions/middlewareList" },
"beforeErrorHandler": { "$ref": "#/definitions/middlewareList" }
}
}
}
}
},
"xsappConfig": { "type": "object" }
},
"definitions": {
"middlewareList": {
"type": "array",
"items": {
"anyOf": [
{ "format": "function" },
{
"type": "object",
"additionalProperties": false,
"properties": {
"path": { "type": "string", "minLength": 1 },
"handler": { "format": "function" }
},
"required": ["handler"]
}
]
}
}
}
}

View file

@ -0,0 +1,18 @@
{
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"format": "plugin-rules",
"properties": {
"name": { "type": "string", "minLength": 1 },
"source": { "$ref": "sourceSchema" },
"target": { "type": "string", "minLength": 1, "format": "relative-uri" },
"destination": { "type": "string", "minLength": 1 },
"csrfProtection": { "type": "boolean" },
"authenticationType": { "type": "string", "enum": ["xsuaa", "ias","basic", "none"] },
"scope": { "$ref": "scopesSchema" }
},
"required": ["name", "source", "destination"]
}
}

View file

@ -0,0 +1,39 @@
{
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"additionalProperties": false,
"properties": {
"uriPattern": { "$ref": "sourceSchema" },
"allowedOrigin": { "format": "validateWhitelist" },
"allowedMethods": {
"type": "array",
"minItems": 1,
"items": {
"type": "string",
"enum": ["DELETE", "GET", "HEAD", "OPTIONS", "POST", "PUT", "TRACE", "PATCH"]
}
},
"allowedHeaders": {
"type": "array",
"minItems": 1,
"items": {
"type": "string",
"minLength": 1
}
},
"allowedCredentials": { "type": "boolean" },
"exposeHeaders": {
"type": "array",
"minItems": 1,
"items": {
"type": "string",
"minLength": 1
}
},
"maxAge": { "type": "integer", "minimum": 1 }
},
"required": ["allowedOrigin", "uriPattern"]
}
}

View file

@ -0,0 +1,30 @@
{
"type": ["string", "array", "object"],
"minProperties": 1,
"additionalProperties": false,
"properties": {
"GET": { "$ref": "#/definitions/scopeTemplate" },
"POST": { "$ref": "#/definitions/scopeTemplate" },
"HEAD": { "$ref": "#/definitions/scopeTemplate" },
"PUT": { "$ref": "#/definitions/scopeTemplate" },
"DELETE": { "$ref": "#/definitions/scopeTemplate" },
"TRACE": { "$ref": "#/definitions/scopeTemplate" },
"PATCH": { "$ref": "#/definitions/scopeTemplate" },
"OPTIONS": { "$ref": "#/definitions/scopeTemplate" },
"CONNECT": { "$ref": "#/definitions/scopeTemplate" },
"default": { "$ref": "#/definitions/scopeTemplate" }
},
"minLength": 1,
"minItems": 1,
"items": { "type": "string", "minLength": 1 },
"definitions": {
"scopeTemplate": {
"id": "scopeTemplate",
"type": ["string", "array"],
"minLength": 1,
"minItems": 1,
"items": { "type": "string", "minLength": 1 }
}
}
}

View file

@ -0,0 +1,10 @@
{
"type": ["string", "object"],
"minLength": 1, "format": "regex",
"additionalProperties": false,
"properties": {
"path": { "type": "string", "minLength": 1, "format": "regex" },
"matchCase": { "type": "boolean" }
},
"required": ["path"]
}

View file

@ -0,0 +1,74 @@
{
"id" : "serviceDestination",
"type": "object",
"additionalProperties": true,
"properties": {
"name": {
"type": "string",
"minLength": 1
},
"type": {
"type": "string",
"minLength": 1,
"enum": [
"HTTP"
]
},
"url": {
"type": "string",
"minLength": 1,
"format": "absolute-uri"
},
"proxyType": {
"type": "string",
"enum": [
"OnPremise",
"Internet",
"PrivateLink"
]
},
"cloudConnectorLocationId": {
"type": "string",
"minLength": 1
},
"forwardAuthToken": {
"type": "boolean"
},
"forwardAuthCertificates": {
"type": "boolean"
},
"IASDependencyName": {
"type": "string",
"minLength": 1
},
"preserveHostHeader": {
"type": "boolean"
},
"dynamicDestination": {
"type": "boolean"
},
"setXForwardedHeaders": {
"type": "boolean"
},
"sap-client": {
"type": "string",
"minLength": 1
},
"user": {
"type": "string",
"minLength": 1
},
"password": {
"type": "string"
}
},
"required": [
"name",
"type",
"url",
"proxyType",
"authentication"
],
"dependencies": { "password": ["user"], "user": ["password"] }
}

View file

@ -0,0 +1,9 @@
{
"type": "object",
"additionalProperties": true,
"properties": {
"clientid": { "type": "string", "minLength": 1 },
"url": { "type": "string", "minLength": 1, "format": "absolute-uri" }
},
"required": ["clientid", "url"]
}

View file

@ -0,0 +1,5 @@
{
"type": "string",
"required": [ {"type": "string", "minLength": 1, "format": "absolute-uri"}],
"additionalProperties": true
}

View file

@ -0,0 +1,14 @@
{
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"format": "hostname-rules",
"properties": {
"protocol": { "type": "string", "minLength": 1 },
"host": { "type": "string", "minLength": 1 },
"port": { "type": ["string", "integer"], "minLength": 1, "format": "valid-port" }
},
"required": ["host"]
}
}

View file

@ -0,0 +1,174 @@
{
"type": "object",
"additionalProperties": false,
"properties": {
"welcomeFile": { "type": "string", "minLength": 1 },
"authenticationMethod": { "type": "string", "enum": ["none", "route"] },
"sessionTimeout": { "type": "integer", "minimum": 1 },
"pluginMetadataEndpoint": { "type": "string", "minLength": 1, "format": "relative-uri" },
"routes": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"format": "route-rules",
"properties": {
"source": { "$ref": "sourceSchema" },
"httpMethods": {
"type": "array",
"minItems": 1,
"items": {
"type": "string",
"enum": ["DELETE", "GET", "HEAD", "OPTIONS", "POST", "PUT", "TRACE", "PATCH"]
}
},
"target": { "type": "string", "minLength": 1, "format": "relative-uri" },
"destination": { "type": "string", "minLength": 1 },
"localDir": { "type": "string", "minLength": 1 },
"csrfProtection": { "type": "boolean" },
"preferLocal": {"type": "boolean"},
"service": { "type": "string" },
"endpoint": { "type": "string" },
"authenticationType": { "type": "string", "enum": ["xsuaa", "ias","basic", "none"] },
"identityProvider": { "type": "string", "minLength": 1},
"dynamicIdentityProvider": { "type": "boolean" },
"scope": { "$ref": "scopesSchema" },
"replace": {
"type": "object",
"required": ["pathSuffixes"],
"additionalProperties": false,
"properties": {
"pathSuffixes": {
"type": "array",
"items": {
"type": "string",
"minLength": 1,
"format": "relative-uri"
}
},
"vars": {
"type": "array",
"items": {
"type": "string",
"minLength": 1
}
},
"services": {
"type": "object"
}
}
},
"cacheControl": { "type": "string", "minLength": 1, "format": "valid-header-value" }
},
"required": ["source"]
}
},
"responseHeaders": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"properties": {
"name": {
"type": "string",
"minLength": 1
},
"value": {
"type": "string",
"minLength": 1
}
},
"required": ["name", "value"]
}
},
"destinations": {
"type": "object",
"format": "destinations-rules",
"additionalProperties": {
"type": "object",
"additionalProperties": false,
"properties": {
"logoutPath": { "type": "string", "minLength": 1, "format": "relative-uri" },
"logoutMethod": { "type": "string", "enum": ["PUT", "POST", "GET"] }
}
}
},
"services": {
"type": "object",
"format": "services-rules",
"additionalProperties": {
"type": "object",
"additionalProperties": false,
"properties": {
"endpoint": { "type": "string", "minLength": 1},
"logoutPath": { "type": "string", "minLength": 1, "format": "relative-uri" },
"logoutMethod": { "type": "string", "enum": ["PUT", "POST", "GET"] }
}
}
},
"logout": {
"type": "object",
"additionalProperties": false,
"format": "logout-rules",
"properties": {
"logoutEndpoint": { "type": "string", "minLength": 1, "format": "relative-uri" },
"logoutPage": { "type": "string", "minLength": 1, "format": "uri" },
"logoutMethod": { "type": "string", "enum": ["POST", "GET"] },
"csrfProtection": {"type": "boolean"}
}
},
"login": {
"type": "object",
"additionalProperties": false,
"properties": {
"callbackEndpoint": { "type": "string", "minLength": 1, "format": "relative-uri" }
},
"required": ["callbackEndpoint"]
},
"whitelistService": {
"type": "object",
"additionalProperties": false,
"properties": {
"endpoint": { "type": "string", "minLength": 1, "format": "relative-uri" }
},
"required": ["endpoint"]
},
"compression": {
"type": "object",
"additionalProperties": false,
"properties": {
"enabled": { "type": "boolean" },
"minSize": { "type": "integer", "minimum": 1 },
"compressResponseMixedTypeContent": { "type": "boolean" }
}
},
"websockets": {
"type": "object",
"additionalProperties": false,
"properties": {
"enabled": { "type": "boolean" }
},
"required": ["enabled"]
},
"errorPage": {
"type": "array",
"additionalProperties": false,
"items": {
"type": "object",
"required": ["status", "file"],
"properties": {
"status": {
"type": ["integer", "array"],
"minimum": 1,
"minItems": 1,
"uniqueItems": true,
"items": { "type": "integer", "minimum": 1 }
},
"file": { "type": "string", "minLength": 1, "format": "local-file" }
}
}
},
"cors": { "$ref": "corsSchema" }
},
"required": ["sessionTimeout"]
}

View file

@ -0,0 +1,36 @@
'use strict';
const _ = require('lodash');
const path = require('path');
const xsenv = require('@sap/xsenv');
const VError = require('verror').VError;
const tracer = require('../utils/logger').getTracer(__filename);
exports.load = function (workingDir) {
let pathToDefaultServices = path.join(workingDir, 'default-services.json');
try {
let xsuaaCredentials = xsenv.getServices({ uaa: matchesUaa }, pathToDefaultServices).uaa;
if (process.env.XSUAA_PRIVATE_KEY){
xsuaaCredentials.key = process.env.XSUAA_PRIVATE_KEY;
}
return xsuaaCredentials;
} catch (e) {
tracer.debug(new VError(e, 'Cannot find service uaa in environment'));
return {};
}
};
function matchesUaa(service) {
let uaaName = process.env.UAA_SERVICE_NAME;
if (uaaName) {
return service.name === uaaName;
}
if (_.includes(service.tags, 'xsuaa')) {
return true;
}
if (service.label === 'user-provided' && service.credentials && _.includes(service.credentials.tags, 'xsuaa')) {
return true;
}
return false;
}

View file

@ -0,0 +1,448 @@
'use strict';
const _ = require('lodash');
const URI = require('urijs');
const path = require('path');
const VError = require('verror').VError;
const JsonValidator = require('../utils/JsonValidator');
const fsUtils = require('../utils/fs-utils');
const businessServiceUtils = require('../utils/business-service-utils');
const xsAppSchema = require('./schemas/xs-app-schema');
const sourceSchema = require('./schemas/refs/source-schema');
const scopesSchema = require('./schemas/refs/scopes-schema');
const cookiesSchema = require('./schemas/cookies-schema');
const pluginsSchema = require('./schemas/plugins-schema');
const headersSchema = require('./schemas/headers-schema');
const whitelistSchema = require('./schemas/whitelist-schema');
const environmentSchema = require('./schemas/environment-schema');
const envDestinationsSchema = require('./schemas/environment-destinations-schema');
const serviceDestinationsSchema = require('./schemas/service-destinations-schema.json');
const destinationsSchema = require('./schemas/destinations-schema.json');
const approuterOptionsSchema = require('./schemas/options-schema');
const uaaOptionsSchema = require('./schemas/uaa-config-schema');
const iasOptionsSchema = require('./schemas/ias-config-schema');
const corsSchema = require('./schemas/refs/cors-schema');
const connectivitySchema = require('./schemas/connectivity-config-schema');
const html5RepoCredSchema = require('./schemas/html5-repo-credentials-schema');
const businessServiceSchema = require('./schemas/business-service-credentials-schema');
const extSessionMgtSchema = require('./schemas/ext-session-mgt-schema');
const destinationUtils = require('../utils/destination-utils');
const vcapUtils = require('../utils/vcap-utils');
const urlSchema = require('./schemas/url-schema.json');
let regexErr;
module.exports = {
validateSessionStoreInstance: function (extSessionMgt) {
const validator = new JsonValidator();
validator.validate(extSessionMgt, extSessionMgtSchema, 'EXT_SESSION_MGT');
if (!vcapUtils.getServiceCredentials({name: extSessionMgt.instanceName})) {
throw new VError('Environment variable EXT_SESSION_MGT_INSTANCE with value ' + extSessionMgt.instanceName + ' defined but no matching external session management service instance is bound');
}
},
validateXsApp: function (configuration, envDestinations, directory) {
addConfigurationDefaults(configuration);
const validator = new JsonValidator();
validator.addSchema('sourceSchema', sourceSchema);
validator.addSchema('scopesSchema', scopesSchema);
validator.addSchema('corsSchema', corsSchema);
validator.addFormat('relative-uri', validateRelativeUri);
validator.addFormat('local-file', validateLocalFile.bind(null, directory));
validator.addFormat('uri', validateUri);
validator.addFormat('regex', validateRegex);
validator.addFormat('valid-header-value', validateHeaderValue);
const destinationServiceBound = destinationUtils.getDestinationServiceCredentials();
validator.addFormat('route-rules', function (route) {
if (!route.destination && !route.localDir && !route.service) {
return 'Route does not have a destination nor a localDir nor a service';
}
if (route.destination) {
if (envDestinations && !envDestinations[route.destination] && !destinationServiceBound && !route.destination.includes('$') && route.destination !== '*') {
return 'Route references unknown destination "' + route.destination + '"';
}
if (route.localDir) {
return 'Route has both localDir and destination';
}
}
if (route.preferLocal && !route.destination) {
return 'Route specifies preferLocal but no destination specified';
}
if (route.service && route.service === 'sap-approuter-userapi') {
return;
}
if (route.service && !process.env.SAAS_APPROUTER) {
const serviceCredentials = businessServiceUtils.getCredentials(route.service, false);
const html5AppsRepoTags = ['html5-apps-repo-rt',
'html5-apps-rt',
'html5-apps-repo-dt',
'html5-apps-dt'];
if (serviceCredentials === null) {
return 'A route requires access to ' + route.service + ' service but the service is not bound.';
}
if (route.destination) {
return 'Route has both destination and service';
}
if (route.localDir) {
return 'Route has both localDir and service';
}
if (route.endpoint) {
const endPoint = route.endpoint;
if (serviceCredentials.endpoints) {
if (!serviceCredentials.endpoints[endPoint]) {
return 'Endpoint object has no attribute named ' + endPoint + ' for service ' + route.service;
}
} else if (!serviceCredentials[endPoint]) {
return 'No ' + endPoint + ' property and no endpoints object provided in the service credentials as endpoint for service ' + route.service;
}
} else {
if (!serviceCredentials.url && !serviceCredentials.uri) {
return 'Service ' + route.service + ' has no endpoints, url or uri defined in credentials';
}
}
if (html5AppsRepoTags.indexOf(route.service) < 0) { // business service, different tag than for html5 apps repo
try {
validator.validate(serviceCredentials, businessServiceSchema, 'business-service');
} catch (err) {
if (err.message.includes('No enum match for:')) { //
return 'User credential service is not supported by approuter ' + err.message;
}
return err.message;
}
} else {
validator.validate(serviceCredentials, html5RepoCredSchema, 'html5-repo-credentials');
}
}
if (route.identityProvider && route.authenticationType && route.authenticationType !== 'xsuaa') {
return 'Route has both identityProvider and authenticationType that is not of type \'xsuaa\'';
}
if (route.localDir) {
const fullPath = path.join(directory, route.localDir);
if (!fsUtils.isDirectory(fullPath)) {
return fullPath + ' is not a directory';
}
if (Array.isArray(route.httpMethods)) {
return 'Route has both localDir and httpMethods';
}
} else {
const forbiddenProperties = ['replace'];
for (let i = 0; i < forbiddenProperties.length; i++) {
if (route[forbiddenProperties[i]]) {
return 'Route has ' + forbiddenProperties[i] + ' with no localDir';
}
}
}
});
validator.addFormat('logout-rules', function (logout) {
if (logout.logoutPage && !logout.logoutEndpoint) {
return 'Logout page is set although logout endpoint is not configured';
}
if (logout.csrfProtection !== undefined && logout.logoutMethod !== 'POST') {
return 'Unable to set csrfProtection when logout method is not POST';
}
});
validator.validate(configuration, xsAppSchema, 'xs-app.json');
const routeDestinations = _.map(configuration.routes || [], 'destination').filter(_.identity);
const unusedDestinations = _.difference(Object.keys(configuration.destinations || {}), routeDestinations);
if (unusedDestinations.length > 0) {
throw new VError('Destination(s) "%s" not used by any route', unusedDestinations);
}
},
validateCookies: function (cookies) {
const validator = new JsonValidator();
validator.validate(cookies, cookiesSchema, 'Cookies environment variable');
},
validateDestinations: function (destinations) {
const validator = new JsonValidator();
validator.addFormat('no-duplicate-names', function (destinations) {
const duplicates = _.chain(destinations).countBy('name').pickBy(function (count) {
return count > 1;
}).keys().value();
if (duplicates.length > 0) {
return 'Duplicate destination names: ' + duplicates;
}
});
validator.addFormat('valid-port', function (proxyPort) {
return validatePort(proxyPort, 'Destination "proxyPort"');
});
validator.addFormat('absolute-uri', validateAbsoluteUri);
validator.addSchema('serviceDestinationsSchema', serviceDestinationsSchema);
validator.addSchema('envDestinationsSchema', envDestinationsSchema);
const validateConnectivityCredentials = this.validateConnectivityCredentials;
let connectivityCreds;
validator.validate(destinations, destinationsSchema, 'destinations');
destinations.forEach(function (destination) {
if (destination.authentication) { // if it destination from destinations service
validator.validate(destination, serviceDestinationsSchema, 'service-destinations');
} else {
validator.validate(destination, envDestinationsSchema, 'environment-destinations');
}
if (destination.forwardAuthToken === true && destination.authentication && destination.authentication !== 'NoAuthentication') {
throw 'Destination \"' + destination.name +
' - ForwardAuthToken parameter cannot be used in destinations with authentication type not equal NoAuthentication';
}
if (destination.proxyType === 'OnPremise') {
if (destination.forwardAuthToken) {
throw 'in destination \"' + destination.name +
' - ForwardAuthToken parameter cannot be used in destinations with proxyType onPremise';
}
if (!connectivityCreds) {
connectivityCreds = vcapUtils.getServiceCredentials({tag: 'connectivity'});
if (!connectivityCreds) {
throw 'Destination \"' + destination.name +
'\" with ProxyType \"OnPremise\" but connectivity service is not bound.';
}
}
validateConnectivityCredentials(connectivityCreds);
}
});
},
validateEnvironmentSettings: function (configuration) {
const validator = new JsonValidator();
validator.addFormat('regexWithCapture', validateRegexWithCapture);
validator.validate(configuration, environmentSchema, 'environment-settings');
},
validatePlugins: function (configuration, envDestinations) {
const validator = new JsonValidator();
validator.addSchema('sourceSchema', sourceSchema);
validator.addSchema('scopesSchema', scopesSchema);
validator.addSchema('corsSchema', corsSchema);
validator.addFormat('regex', validateRegex);
validator.addFormat('relative-uri', validateRelativeUri);
validator.addFormat('plugin-rules', function (plugin) {
if (envDestinations && plugin.destination && !envDestinations[plugin.destination]) {
return 'Plugin references destination "' + plugin.destination + '", which cannot be found in the environment';
}
});
validator.validate(configuration, pluginsSchema, 'plugins');
},
validateHeaders: function (configuration) {
const validator = new JsonValidator();
validator.addFormat('valid-header-value', validateHeaderValue);
validator.addFormat('headers-rules', function (header) {
const key = Object.keys(header)[0];
const headerNamePattern = /^[!#$%&'*+\-.^_`|~0-9a-zA-Z]+$/; // http://tools.ietf.org/html/rfc7230#section-3.2.6
if (!headerNamePattern.test(key)) {
return 'Header does not have a valid name';
}
const headerName = key.match(headerNamePattern)[0];
if (headerName.toLowerCase() === 'set-cookie' || headerName.toLowerCase() === 'cookie') {
return 'Headers "set-cookie" and "cookie" are not allowed in the additional headers';
}
});
validator.validate(configuration, headersSchema, 'http-headers');
},
validateWhitelist: function (whitelist) {
const validator = new JsonValidator();
validator.addFormat('hostname-rules', function (listItem) {
const regexHostName = /^(\*\.)?(([a-zA-Z]|[a-zA-Z][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z]|[A-Za-z][A-Za-z0-9\-]*[A-Za-z0-9])$/;
const regexIPHostName = /^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$/;
if (!(regexHostName.test(listItem.host) || regexIPHostName.test(listItem.host))) {
return 'Whitelist item\'s host "' + listItem.host + '" is not correct. Possible values allow valid hostname, IP or hostname prefixed with *.';
}
});
validator.addFormat('valid-port', function (port) {
return validatePort(port, 'Whitelist item\'s port');
});
validator.validate(whitelist, whitelistSchema, 'clickjack-whitelist');
},
validateApprouterStartOptions: function (options) {
const validator = new JsonValidator();
validator.addFormat('valid-port', function (port) {
if (port === 0) {
return;
}
return validatePort(port, 'Approuter port');
});
validator.addFormat('function', function functionFormat(data) {
if (typeof data !== 'function') {
return 'Function expected';
}
});
validator.validate(options, approuterOptionsSchema, 'options');
},
validateUaaOptions: function (options) {
const validator = new JsonValidator();
validator.addFormat('absolute-uri', validateAbsoluteUri);
validator.validate(options, uaaOptionsSchema, 'uaa-configuration');
},
validateIasOptions: function (options) {
const validator = new JsonValidator();
validator.addFormat('absolute-uri', validateAbsoluteUri);
validator.validate(options, iasOptionsSchema, 'ias-configuration');
},
validateConnectivityCredentials: function (options) {
const validator = new JsonValidator();
validator.addFormat('absolute-uri', validateAbsoluteUri);
validator.validate(options, connectivitySchema, 'connectivity-configuration');
},
validateClientCredentials: function (options) {
const validator = new JsonValidator();
validator.addFormat('absolute-uri', validateAbsoluteUri);
if (options.uaa && options.uaa.clientid && options.uaa.clientid.length > 0
&& ((options.uaa.clientsecret && options.uaa.clientsecret.length > 0) || options.uaa.certificate)
&& options.uaa.url && options.uaa.url.length > 0) {
validator.validate(options.uaa.url, urlSchema, 'url-schema');
return;
} else if (options.clientid && options.clientid.length > 0
&& ((options.clientsecret && options.clientsecret.length > 0) || options.certificate)
&& options.url && options.url.length > 0) {
validator.validate(options.url, urlSchema, 'url-schema');
return;
}
throw new Error('No clientid or clientsercret provided');
},
validateCors: function (options) {
const validator = new JsonValidator();
validator.addFormat('regex', validateRegex);
validator.addFormat('validateWhitelist', exports.validateWhitelist);
validator.addSchema('sourceSchema', sourceSchema);
validator.validate(options, corsSchema, 'cors-configuration');
},
validateType: function(value, property, expectedType) {
if (value && typeof value !== expectedType) {
throw new VError('"%s" is of type "%s" instead of type "%s"', property, typeof value, expectedType);
}
},
checkStringOrRegex: function (value) {
try {
regexErr = validateRegexWithCapture(value);
} catch (exception) {
regexErr = true;
}
if (!regexErr) {
return true;
}
if (typeof value === 'string') {
return true;
}
return false;
}
};
function addConfigurationDefaults(configuration) {
const defaultSessionTimeoutInMinutes = 15;
const defaultLoginCallback = '/login/callback';
const websocketsEnabledByDefault = false;
const compressionEnabledByDefault = true;
_.defaultsDeep(configuration, {compression: {enabled: compressionEnabledByDefault}});
configuration.sessionTimeout = configuration.sessionTimeout || defaultSessionTimeoutInMinutes;
configuration.login = getPropertyValue(configuration, 'login', {callbackEndpoint: defaultLoginCallback});
configuration.websockets = getPropertyValue(configuration, 'websockets', {enabled: websocketsEnabledByDefault});
}
function getPropertyValue(configObject, propertyName, defaultValue) {
if (configObject.hasOwnProperty(propertyName)) {
return configObject[propertyName];
}
return defaultValue;
}
function validateRelativeUri(relativeUri) {
const components = URI.parse(relativeUri);
if (components.protocol || components.hostname) {
return 'URI must be a relative path';
}
}
function validateLocalFile(directory, file) {
const fullPath = path.join(directory, file);
if (!fsUtils.isFile(fullPath)) {
return fullPath + ' is not a file';
}
}
function validateAbsoluteUri(uri) {
const components = URI.parse(uri);
if (!components.protocol || !components.hostname) {
return 'URI must be absolute';
}
const supportedProtocols = ['http', 'https', 'ws', 'wss'];
if (!_.includes(supportedProtocols, components.protocol)) {
return 'URI has unsupported protocol, supported protocols are ' + supportedProtocols;
}
}
function validateUri(uri) {
const components = URI.parse(uri);
if (components.protocol && components.protocol !== 'http' && components.protocol !== 'https') {
return 'Supported schemes are \'http\' and \'https\'';
}
}
function validateRegex(regex) {
const regexToCheck = _.isObject(regex) ? regex.path : regex;
try {
RegExp(regexToCheck);
} catch (exception) {
return exception;
}
}
function validateRegexWithCapture(regex) {
const regexError = validateRegex(regex);
if (regexError) {
return regexError;
}
if (regex.indexOf('(') === -1) {
return 'regular expression must contain a capturing group';
}
}
function validatePort(port, messagePrefix) {
const portMinValue = 1;
const portMaxValue = 65535;
if (typeof port === 'string' && !/^[1-9]\d*$/.test(port)) {
return messagePrefix + ' value is string, which cannot be parsed as positive integer';
}
port = parseFloat(port);
if (port < portMinValue || port > portMaxValue) {
return messagePrefix + ' value has to be string or integer between ' + portMinValue + '-' + portMaxValue;
}
}
function validateHeaderValue(headerValue) {
// Inspired by Node.js: https://github.com/nodejs/node/blob/master/lib/_http_outgoing.js
const headerValueIsValid = headerValue.split('').map(function (character) {
return character.charCodeAt(0);
}).every(function (charCode) {
return (charCode > 31 && charCode <= 255 && charCode !== 127) || charCode === 9;
});
if (!headerValueIsValid) {
return 'The header content contains invalid characters';
}
}

View file

@ -0,0 +1,14 @@
'use strict';
const connect = require('../utils/connect');
module.exports = function () {
let app = connect();
app._store = {};
app.set = function (key, value) {
app._store[key] = value;
};
app.get = function (key) {
return app._store[key];
};
return app;
};

80
app1/node_modules/@sap/approuter/lib/connect/utils.js generated vendored Normal file
View file

@ -0,0 +1,80 @@
'use strict';
const loggingUtils = require('./../utils/logger');
const tracer = loggingUtils.getTracer(__filename);
const querystring = require('querystring');
const url = require('url');
const VError = require('verror').VError;
function setPropertiesToRequest(req, app) {
req.app = app;
req.protocol = req.connection.encrypted ? 'https' : 'http';
req.query = querystring.parse(url.parse(req.url).query || ''); // needed by passport-oauth
}
function validateHeaders(req) {
const reqHeaders = req.headers;
const routerConfig = req.routerConfig || (req.app ? req.app.get('mainRouterConfig') : null);
if (detectHRS(reqHeaders)){
return new VError('Failed to validate HRS, both Transfer-Encoding and Content-Length headers are presented in the request headers');
}
if (reqHeaders['x-forwarded-host']) {
if (!isValidXFwdHost(reqHeaders['x-forwarded-host'], routerConfig)) {
return new VError('Failed to validate x-forwarded-host:' + reqHeaders['x-forwarded-host']);
}
}
return null;
}
function detectHRS(headers) {
return headers['transfer-encoding'] && headers['content-length'];
}
function isValidXFwdHost(fwdHostHeader, routerConfig) {
if (!routerConfig || !routerConfig.enableXForwardHostValidation) {
// Will run validation if explicitly set to true
tracer.info('x-forwarded-host header will not be validated');
return true;
}
const lastColonIndex = fwdHostHeader.lastIndexOf(':');
let hostname, port;
if (lastColonIndex !== -1) {
hostname = fwdHostHeader.slice(0, lastColonIndex);
port = fwdHostHeader.slice(lastColonIndex + 1);
} else {
hostname = fwdHostHeader;
}
return isHostnameValid(hostname) && isPortValid(port);
}
function isPortValid(port) {
const portRegexExp = /^([1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$/g;
return port ? portRegexExp.test(port) : true;
}
function isHostnameValid(hostname) {
// Check also for underscore
const hostnameRegExp = /^[a-z0-9-_.]{1,253}\.?$/gi;
if (!hostnameRegExp.test(hostname)) {
return false;
}
// Trailing dot, if any, is not counted in the hostname max length limitation
if (hostname.endsWith('.')) {
hostname = hostname.slice(0, -1);
}
// Split hostname if it has dots
const hostnameParts = hostname.split('.');
const maxPartLength = 64;
const hostnamePartRegExp = /^[a-z0-9-_]+$/gi;
const validHostname = hostnameParts.every((part) => {
hostnamePartRegExp.lastIndex = 0;
return part.length < maxPartLength && hostnamePartRegExp.test(part);
});
return validHostname;
}
module.exports = {
setPropertiesToRequest, validateHeaders, isPortValid, isHostnameValid
};

24
app1/node_modules/@sap/approuter/lib/environment.js generated vendored Normal file
View file

@ -0,0 +1,24 @@
'use strict';
const path = require('path');
const tracer = require('./utils/logger').getTracer(__filename);
const fsUtils = require('./utils/fs-utils');
module.exports = {
getPort: function (port) {
// port 0 is valid
return (port !== undefined) ? port : (process.env.PORT || '5000');
},
getWorkingDirectory: function (workingDir, xsappConfig) {
let wdir = workingDir || process.cwd();
let xsappFile = path.join(wdir, 'xs-app.json');
tracer.info('Checking file existence: ' + xsappFile);
if (!xsappConfig && !fsUtils.isFile(xsappFile)) {
throw new Error(`Neither xsappConfig nor ${xsappFile} files were found`);
}
tracer.info('Using working directory: ' + wdir);
return wdir;
}
};

View file

@ -0,0 +1,31 @@
'use strict';
const assert = require('assert');
module.exports = MiddlewareList;
function MiddlewareList() {
this._middleware = [];
}
/**
* Adds a middleware for request handling.
*
* Same as connect.use().
*
* @param {String} [path] - path prefix
* @param {Function} handler - request handler middleware
* @return this for chaining
*/
MiddlewareList.prototype.use = function (path, handler) {
if (handler !== undefined) {
assert(typeof path === 'string', 'path should be a string');
assert(typeof handler === 'function', 'handler should be a function');
this._middleware.push({ path: path, handler: handler });
} else {
handler = path;
assert(typeof handler === 'function', 'handler should be a function');
this._middleware.push(handler);
}
return this;
};

View file

@ -0,0 +1,36 @@
'use strict';
const assert = require('assert');
module.exports = Extensions;
function Extensions(extensions) {
assert(!extensions || Array.isArray(extensions),
'extensions should be an array');
if (!(this instanceof Extensions)) {
return new Extensions(extensions);
}
this.extensions = extensions;
}
Extensions.prototype.insertMiddleware = function(slot, connectApp) {
if (!this.extensions) { return; }
for (let i = 0; i < this.extensions.length; ++i) {
let insert = this.extensions[i].insertMiddleware;
if (insert && insert[slot]) {
let middleware = insert[slot];
for (let j = 0; j < middleware.length; ++j) {
let m = middleware[j];
if (typeof m === 'function') {
connectApp.use(m);
} else if (m.path) {
connectApp.use(m.path, m.handler);
} else {
connectApp.use(m.handler);
}
}
}
}
};

View file

@ -0,0 +1,120 @@
'use strict';
const _ = require('lodash');
const request = require('../utils/request-utils');
const passportUtils = require('../passport/utils');
const sessionExt = require('../utils/session-ext');
// eslint-disable-next-line no-undef
const tracer = require('../utils/logger').getTracer(__filename);
const X_FRAME_OPTIONS = 'x-frame-options';
const CSP_HEADER_NAME = 'Content-Security-Policy';
module.exports = async function additionalHeaders(req, res, next) {
let routerConfig = req.routerConfig;
let additionalHeaders = routerConfig.additionalHeaders.slice();
additionalHeaders = modifyAdditionalHeadersFromResponseHeaders(req,additionalHeaders);
if (routerConfig.sendXFrameOptions) {
let xFrameHeader = _.find(additionalHeaders, function searchXFrame(header) { return !!header[X_FRAME_OPTIONS]; });
if (!xFrameHeader) {
res.setHeader(X_FRAME_OPTIONS, 'SAMEORIGIN');
}
}
additionalHeaders.forEach(function addAdditionalHeader(header) {
_.forEach(header, function setHeader(value, name) {
let currentValue = res.getHeader(name);
if (currentValue === undefined) {
res.setHeader(name, value);
} else if (Array.isArray(currentValue)) {
currentValue.push(value);
} else { // string
res.setHeader(name, [currentValue, value]);
}
});
});
try {
await module.exports.getCSPFrameAncestorsFromSubaccount(req, res, routerConfig);
} catch (err){
return next(err);
}
next();
};
function getLandscapeDomain(cfApiUrl) {
let url = new URL(cfApiUrl);
let parts = url.hostname.split('.');
parts.shift(); // remove 'api'
parts.shift(); // remove 'cf'
return parts.join('.');
}
module.exports.getCSPFrameAncestorsFromSubaccount = async function getCSPFrameAncestorsFromSubaccount(req, res, routerConfig) {
if (routerConfig.enableCSPheaders) {
let subdomain = passportUtils.getTenantInfo(req).tenant;
if (!subdomain) {
return;
}
const landscapeDomain = getLandscapeDomain(JSON.parse(process.env.VCAP_APPLICATION).cf_api);
if (!landscapeDomain) {
return;
}
const apiURL = `https://api.authentication.${landscapeDomain}/sap/rest/authorization/v2/securitySettings/public?subdomain=${subdomain}`;
// Check if cspHeaders are in the session
if (req.session && req.session.cspHeaders) {
res.setHeader(CSP_HEADER_NAME, req.session.cspHeaders);
} else {
let result = await request.axiosRequest('get',{url: apiURL});
if (result.error || result.response.statusCode !== 200){
throw new Error('Failed to fetch allowed domains from xsuaa ' + result.error
+ (result.response && result.response.statusCode ? `response status ${result.response.statusCode}` : ''));
}
const resultBody = result.body && JSON.parse(result.body);
if (resultBody){
const cspHeaders = `frame-ancestors ${resultBody.iframeDomains};`;
res.setHeader(CSP_HEADER_NAME, cspHeaders);
// Store cspHeaders in the session
if (req.session) {
sessionExt.update(req.session, function(session) {
session.cspHeaders = cspHeaders;
});
}
}
}
}
};
function modifyAdditionalHeadersFromResponseHeaders(req, additionalHeaders) {
let foundIndex;
if (!req.routerConfig.appConfig.responseHeaders){
return additionalHeaders;
}
req.routerConfig.appConfig.responseHeaders.forEach(function (responseHeader) {
foundIndex = -1;
for (let i = 0; i < additionalHeaders.length; i++) {
if (additionalHeaders[i].hasOwnProperty(responseHeader.name.toLowerCase())) {
foundIndex = i;
break;
}
}
if (foundIndex > -1) {
additionalHeaders[foundIndex] = {
[responseHeader.name]: responseHeader.value
};
tracer.debug('found response header name %s in additional headers, so update response header value %s instead of previous value', responseHeader.name, responseHeader.value);
}
else {
additionalHeaders.push({
[responseHeader.name]: responseHeader.value
});
}
});
return additionalHeaders;
}

View file

@ -0,0 +1,25 @@
'use strict';
module.exports = function attachRouterConfig(req, res, next) {
req.routerConfig = req.app.get('mainRouterConfig');
if (!req.routerConfig.getRouterConfig) {
return next();
}
let startTime = Date.now();
req.routerConfig.getRouterConfig(req, function(err, customRouterConfig) {
if (err) { return next(err); }
traceGetRouterConfig(req.loggingContext, customRouterConfig, startTime);
req.routerConfig = customRouterConfig || req.routerConfig;
next();
});
};
function traceGetRouterConfig(loggingContext, customRouterConfig, startTime) {
let tracer = loggingContext.getTracer(__filename);
let time = Date.now() - startTime;
if (customRouterConfig) {
tracer.debug('getRouterConfig returned a custom routerConfig in %d ms', time);
} else {
tracer.debug('getRouterConfig completed in %d ms without a custom routerConfig', time);
}
}

View file

@ -0,0 +1,37 @@
'use strict';
const tracer = require('../utils/logger').getTracer(__filename);
const iasUtils = require('../utils/ias-utils');
const passportUtils = require('../passport/utils');
const vcapUtils = require('../utils/vcap-utils');
module.exports = function attachZoneInfo(req, res, next) {
let routerConfig = req && req.app && req.app.get('mainRouterConfig');
let iasConfig = req.extIasConfigOptions || (routerConfig && routerConfig.iasConfig && routerConfig.iasConfig.options);
if (!iasConfig || !iasConfig.domain || req.zoneInfo) {
return next();
}
let uaadomain = routerConfig.uaaConfig && routerConfig.uaaConfig.options && routerConfig.uaaConfig.options.uaadomain;
let tenant = null;
if (process.env.TENANT_HOST_PATTERN) {
tenant = passportUtils.getUrlTenant(req);
if (!tenant) {
return next('Failed to extract tenant from tenant host pattern: ' + process.env.TENANT_HOST_PATTERN);
}
if (!uaadomain){
let smsCredentials = vcapUtils.getServiceCredentials({ label: 'subscription-manager' });
uaadomain = smsCredentials && smsCredentials.uaadomain;
}
}
let logger = req && req.loggingContext;
iasUtils.getZoneInfo(tenant, iasConfig, uaadomain, logger, (err, zoneInfo) => {
if (err){
return next(err);
}
if (zoneInfo && Object.keys(zoneInfo).length > 0) {
req.zoneInfo = zoneInfo;
tracer.info('Zone info found ' + JSON.stringify(zoneInfo));
}
next();
});
};

View file

@ -0,0 +1,81 @@
'use strict';
const urlUtils = require('../utils/url-utils');
const uaaUtils = require('../utils/uaa-utils');
const loggerUtil = require('../utils/logger');
const logger = loggerUtil.getLogger('/authorization-handler');
module.exports = function checkAuthorization(req) {
if (!req.internalUrl || req.internalUrl.route.authenticationType === 'none') {
return {isAuthorized: true};
}
const scopesMessage = 'You do not have the required scopes to access this resource.';
const tenantMessage = 'You cannot use session from another tenant.';
const loggingData = loggerUtil.getAuditLogAdditionalData(req);
let auditLogMessage;
const uaaConfig = req.routerConfig && req.routerConfig.uaaConfig;
const tenantMode = uaaConfig && uaaConfig.options && uaaConfig.options.tenantmode;
if (tenantMode === 'shared') {
const tenantHostPattern = uaaConfig && uaaConfig.tenantHostPattern;
if (tenantHostPattern) {
const urlTenant = uaaUtils.retrieveTenantFromURL(urlUtils.getAppRouterHost(req), tenantHostPattern);
if (urlTenant && req.session && req.session.user && req.session.user.tenant &&
urlTenant !== req.session.user.tenant) {
auditLogMessage = 'User not authorized, IP: ' +
loggingData.IP + ', JWT token tenant: ' + req.session.user.tenant + ', URL tenant: ' + urlTenant;
loggerUtil.writeToAuditLog(req, loggingData, auditLogMessage, function (err) {
if (err) {
logger.error(err,'Failed to write to audit log');
}
});
return {isAuthorized: false, message: tenantMessage};
}
}
}
let routeScopes = req.internalUrl.route.scope;
if (!routeScopes) {
return {isAuthorized: true};
}
if (!Array.isArray(routeScopes)) {
routeScopes = routeScopes[req.method] || routeScopes.default || [];
}
// managed approuter
const bsScopes = req.session && req.session.user && req.destinationCredentials && req.destinationCredentials.uniqueServiceName
&& req.session.user.businessServices && req.session.user.businessServices[req.destinationCredentials.uniqueServiceName] &&
req.session.user.businessServices[req.destinationCredentials.uniqueServiceName].scopes;
// standalone approuter
let oauthScopes = req.session && req.session.user && req.session.user.scopes;
oauthScopes = bsScopes ? bsScopes : oauthScopes;
if (!oauthScopes) {
auditLogMessage = 'User not authorized, the token does not contain scopes, source of route: ' + req.internalUrl.route.source + ', IP: ' +
loggingData.IP + ', required scopes: ' + routeScopes + ', user scopes (are missing): ' + oauthScopes;
loggerUtil.writeToAuditLog(req, loggingData, auditLogMessage, function (err) {
if (err) {
logger.error(err,'Failed to write to audit log');
}
});
return {isAuthorized: false, message: scopesMessage};
}
const isAuthorized = routeScopes.some(function (element) {
return oauthScopes.indexOf(element) > -1;
});
const result = {isAuthorized: isAuthorized};
if (!isAuthorized) {
auditLogMessage = 'User not authorized, source of route: ' + req.internalUrl.route.source + ', IP: ' +
loggingData.IP + ', required scopes: ' + routeScopes + ', user scopes: ' + oauthScopes;
loggerUtil.writeToAuditLog(req, loggingData, auditLogMessage, function (err) {
if (err) {
logger.error(err,'Failed to write to audit log');
}
});
result.message = scopesMessage;
}
return result;
};

View file

@ -0,0 +1,15 @@
'use strict';
const checkAuthorization = require('./authorization-handler');
module.exports = function authorizationCheck(req, res, next) {
let authorizationResult = checkAuthorization (req);
if (authorizationResult.isAuthorized) {
next();
} else {
let error = new Error(authorizationResult.message);
error.status = 403;
next(error);
}
};

View file

@ -0,0 +1,22 @@
'use strict';
const deepmerge = require('deepmerge');
const sessionExt = require('../utils/session-ext');
module.exports = function cacheRequestData (req, res, next) {
if (!req.toBeCachedOnSession || !req.toBeCachedOnSession.user) {
return next ();
}
if (req.session && req.session.user) {
let toBeCachedOnSession = Object.assign({}, req.toBeCachedOnSession);
sessionExt.update(req.session, function(session) {
if (session) {
session.user = deepmerge(session.user, toBeCachedOnSession.user);
}
});
}
delete req.toBeCachedOnSession;
next ();
};

View file

@ -0,0 +1,7 @@
'use strict';
module.exports = function notProcessed(req, res, next) {
let error = new Error('Cannot ' + req.method + ' ' + (req.originalUrl || req.url));
error.status = 404;
next(error);
};

View file

@ -0,0 +1,123 @@
'use strict';
const tokenUtils = require('../utils/token-utils');
const headerUtils = require('../utils/header-util');
const uaaUtils = require('../utils/uaa-utils');
const xsenv = require('@sap/xsenv');
const CONNECTIVITY = 'connectivity';
const XSUAA = 'xsuaa';
const vcapUtils = require('../utils/vcap-utils');
let isConnectivityBound = null;
module.exports = function renewToken(req, res, next) {
if (req.internalUrl && req.internalUrl.route && req.internalUrl.route.service) {
assignServiceIfNeeded(req, req.internalUrl.route.service);
let businessServiceData = getBusinessServiceData(req);
if (businessServiceData) {
tokenUtils.loadClientCredentialsToken(req.app, headerUtils.getCorrelationId(req), businessServiceData.credentials, businessServiceData.service, function (err) {
return next(err);
});
} else {
return next();
}
} else if (req.internalUrl && req.internalUrl.route && req.internalUrl.route.destination) {
isConnectivityBound = isConnectivityBound === null ?
vcapUtils.getServiceCredentials({ label: CONNECTIVITY }) !== null : isConnectivityBound;
if (isConnectivityBound) {
assignServiceIfNeeded(req, CONNECTIVITY);
let businessServiceData = getBusinessServiceData(req, CONNECTIVITY);
if (businessServiceData) {
tokenUtils.loadClientCredentialsToken(req.app, headerUtils.getCorrelationId(req), businessServiceData.credentials, businessServiceData.service, function (err) {
if (err) {
return next(err);
}
return exchangeConnectivityToken(req, next);
});
} else {
return exchangeConnectivityToken(req, next);
}
}
else {
return next();
}
} else {
return next();
}
};
function getClientCredentialTokenByTenant(correlationId, tenant, credentials, zoneInfo, cb) {
return tokenUtils.getClientCredentialsTokenByTenant(correlationId, tenant, credentials, zoneInfo, cb);
}
function getServiceByName(requestedServiceName) {
requestedServiceName = requestedServiceName === 'html5-apps-repo-rt' ? 'html5-apps-repo' : requestedServiceName;
let services = xsenv.readServices();
if (services) {
for (let serviceName in services) {
let service = services[serviceName];
if (service.label === requestedServiceName && service.credentials) {
return service.credentials.uaa ? service.credentials.uaa : service.credentials;
}
}
}
return null;
}
function exchangeConnectivityToken(req, next) {
let connectivityCredentials = getServiceByName(CONNECTIVITY);
let ownUAACredentials = getServiceByName(XSUAA);
if (!connectivityCredentials || !ownUAACredentials) {
return next();
}
uaaUtils.getUaaConfig(req, function (err, uaaOptions) {
if (err || !uaaOptions) {
return next(err ? err : 'Invalid UAA options');
}
req.tenant = uaaOptions.tenant;
connectivityCredentials.label = CONNECTIVITY;
if ((uaaOptions.tenantmode === 'shared' && uaaOptions.tenant !== ownUAACredentials.identityzone) &&
((req.app.services[CONNECTIVITY][uaaOptions.tenant] &&
req.app.services[CONNECTIVITY][uaaOptions.tenant].token &&
req.app.services[CONNECTIVITY][uaaOptions.tenant].token.tokenRefreshTimestamp - Date.now() <= 0) ||
!req.app.services[CONNECTIVITY][uaaOptions.tenant])) {
getClientCredentialTokenByTenant(headerUtils.getCorrelationId(req), uaaOptions.tenant, connectivityCredentials, req.zoneInfo, function (err, token) {
if (err) {
return next(err);
}
req.app.services[CONNECTIVITY][uaaOptions.tenant] = {
token: token
};
return next();
});
} else {
return next();
}
});
}
function assignServiceIfNeeded(req, serviceName) {
if (!req.app.services) {
req.app.services = {};
}
if (!req.app.services[serviceName]) {
req.app.services[serviceName] = {};
}
}
function getBusinessServiceData(req, serviceNamePrm) {
const serviceName = serviceNamePrm || req.internalUrl.route.service;
let serviceFromEnvCredentials = null;
if (req && req.app && req.app.services && req.app.services[serviceName]) {
let service = req.app.services[serviceName];
if (!service.token || (service.token && service.token.tokenRefreshTimestamp - Date.now() <= 0)) {
serviceFromEnvCredentials = getServiceByName(serviceName);
if (!serviceFromEnvCredentials) {
return null;
}
return {
credentials: serviceFromEnvCredentials,
service: serviceName
};
}
}
}

View file

@ -0,0 +1,26 @@
'use strict';
const compression = require('compression');
const contentType = require('../utils/content-type');
module.exports = function(conf) {
let opt = {};
conf = conf || {};
opt.threshold = conf.minSize;
opt.filter = contentType.isResponseContentCompressible;
const compfnc = function(req, res, next) {
// WORKAROUND for compression bug/PR https://github.com/expressjs/compression/pull/155 when using http2 - to remove
req.routerConfig && req.routerConfig.http2Support && (res._implicitHeader = function() {
// According to https://datatracker.ietf.org/doc/html/rfc9113, remove Connection-Specific Header Fields
this.removeHeader('transfer-encoding');
this.removeHeader('proxy-connection');
this.removeHeader('keep-alive');
this.removeHeader('upgrade');
this.writeHead(this.statusCode);
});
return compression(opt)(req,res, next);
};
return compfnc;
};

View file

@ -0,0 +1,23 @@
'use strict';
const connectUtils = require('../connect/utils');
const urlUtils = require('./../utils/url-utils');
module.exports = function (app) {
return function connectUtilsMiddleWare(req, res, next) {
connectUtils.setPropertiesToRequest(req, app);
let err = connectUtils.validateHeaders(req);
if (err !== null) {
err.status = 400;
return next(err);
}
if ((process.env.DISABLE_PATHNAME_VALIDATION || '').toLowerCase() !== 'true') {
err = urlUtils.validatePathname(req);
if (err !== null) {
err.status = 400;
return next(err);
}
}
next();
};
};

View file

@ -0,0 +1,253 @@
'use strict';
const _ = require ('lodash');
const url = require ('url');
const urlUtils = require('../utils/url-utils');
const whitelistUtil = require('../utils/whitelist-utils');
module.exports = function processCors(req, res, next) {
if (!req.headers['origin']) {
return next();
}
let originURL = url.parse (req.headers['origin']);
if (!isOriginValid (originURL)) {
return next(getForbiddenError ('Invalid origin header value.'));
}
if (isLocalOrigin (req, req.headers['origin'])) {
return next();
}
let corsDefinition = getCorsDefinition (req);
if (!corsDefinition) {
return next();
}
let corsConfigWithDefaults = fillDefaultsCorsConfig(corsDefinition);
if (req.method === 'OPTIONS' && req.headers['access-control-request-method']) {
processPreflight(req, res, originURL, corsConfigWithDefaults, next);
}
else {
processActualRequest (req, res, originURL, corsConfigWithDefaults, next);
}
};
function getCorsDefinition (req) {
let envCors = req.routerConfig.cors;
let xsappCorsData;
let xsappCors = req.routerConfig.appConfig.cors;
if (!envCors && !xsappCors)
{
return;
}
if (envCors && !xsappCors)
{
return _.find(envCors, function (envCors) {
return envCors.uriPattern.test(req.url);
});
}
if (!envCors && xsappCors)
{
return _.find(xsappCors, function (xsappCors) {
return xsappCors.uriPattern.test(req.url);
});
}
if (envCors && xsappCors)
{
xsappCorsData = _.find(xsappCors, function (xsappCors) {
return xsappCors.uriPattern.test(req.url);
});
if (!xsappCorsData)
{
return _.find(envCors, function (envCors) {
return envCors.uriPattern.test(req.url);
});
}
else
{
return xsappCorsData;
}
}
}
function isLocalOrigin (req, origin) {
let appRouterUrl = urlUtils.parse(urlUtils.buildAppRouterUrl(req));
let target = '';
let protocol = appRouterUrl.protocol.slice(0, -1);
if (!protocol) {
return false;
}
target += protocol + '://';
let host = appRouterUrl.hostname;
if (!host) {
return false;
}
target += host;
let port = appRouterUrl.port;
if (port && ((protocol === 'http' && port !== 80) ||
(protocol === 'https' && port !== 443))) {
target += ':' + port;
}
return (target.toLowerCase() === origin.toLowerCase());
}
function isOriginValid (originURL) {
if (!originURL.protocol || !originURL.hostname || _.includes(originURL.href, '%')) {
return false;
}
return true;
}
function getForbiddenError (errorString) {
let error = new Error(errorString);
error.status = 403;
return error;
}
function isOriginAllowed (corsDefinition, originURL) {
if (!originURL.port) {
if (originURL.protocol.slice(0, -1) === 'http') {
originURL.host += ':80';
}
else if (originURL.protocol.slice(0, -1) === 'https') {
originURL.host += ':443';
}
}
return whitelistUtil.matchWithUrl(corsDefinition.allowedOrigin, originURL);
}
function isAnyOriginAllowed (corsDefinition) {
return corsDefinition.allowedOrigin.some (function (allowedOrigin) {
return (allowedOrigin.host === '*' && !allowedOrigin.protocol && !allowedOrigin.port);
});
}
function isMethodAllowed (req, corsDefinition) {
return _.includes(corsDefinition.allowedMethods, req.method);
}
function processActualRequest (req, res, originURL, corsDefinition, next) {
if (!isOriginAllowed (corsDefinition, originURL)) {
return next(getForbiddenError ('Origin is not allowed.'));
}
if (!isMethodAllowed (req, corsDefinition)) {
return next(getForbiddenError ('Method is not allowed.'));
}
let anyOriginAllowed = isAnyOriginAllowed (corsDefinition);
if (anyOriginAllowed && !corsDefinition.allowedCredentials) {
res.setHeader('access-control-allow-origin', '*');
}
else {
res.setHeader('access-control-allow-origin', req.headers['origin']);
}
if (corsDefinition.allowedCredentials) {
res.setHeader('access-control-allow-credentials', 'true');
}
if (corsDefinition.exposeHeaders && corsDefinition.exposeHeaders.length > 0) {
let exposedHeaders = corsDefinition.exposeHeaders.join(', ');
res.setHeader('access-control-expose-headers', exposedHeaders);
}
next();
}
let DEFAULT_ALLOWED_METHODS = [
'GET',
'POST',
'HEAD',
'OPTIONS'
];
let DEFAULT_ALLOWED_HEADERS = [
'Origin',
'Accept',
'X-Requested-With',
'Content-Type',
'Access-Control-Request-Method',
'Access-Control-Request-Headers'
];
function processPreflight(req, res, originURL, corsDefinition, next) {
let responseHeaders = {};
if (!isOriginAllowed(corsDefinition, originURL)) {
return next(getForbiddenError('Origin not allowed'));
}
let requestMethod = req.headers['access-control-request-method'];
let hasMethodMatch = _.includes(corsDefinition.allowedMethods, requestMethod);
if (!hasMethodMatch) {
return next(getForbiddenError('Access-Control-Request-Method is not allowed'));
}
let header = req.headers['access-control-request-headers'];
if (header) {
let requestHeaders = header.replace(' ', '').split(',');
let headersMatching = _.intersection(_.map(requestHeaders, _.toLower), _.map(corsDefinition.allowedHeaders, _.toLower));
if (headersMatching.length !== requestHeaders.length) {
return next(getForbiddenError('Not all requested Access-Control-Request-Headers are allowed'));
}
}
let origin = req.headers['origin'];
let supportCredentials = corsDefinition.allowedCredentials;
if (supportCredentials) {
responseHeaders['access-control-allow-credentials'] = 'true';
responseHeaders['access-control-allow-origin'] = origin;
}
else {
if (isAnyOriginAllowed(corsDefinition)) {
responseHeaders['access-control-allow-origin'] = '*';
}
else {
responseHeaders['access-control-allow-origin'] = origin;
}
}
let maxAge = corsDefinition.maxAge;
if (maxAge > 0) {
responseHeaders['access-control-max-age'] = maxAge;
}
responseHeaders['access-control-allow-methods'] = requestMethod;
responseHeaders['access-control-allow-headers'] = corsDefinition.allowedHeaders.join(', ');
send(res, responseHeaders);
}
function fillDefaultsCorsConfig(corsConfig) {
let corsConfigWithDefaults = {};
corsConfigWithDefaults.uriPattern = corsConfig.uriPattern;
corsConfigWithDefaults.allowedOrigin = Array.isArray(corsConfig.allowedOrigin) && corsConfig.allowedOrigin.length > 0 ? corsConfig.allowedOrigin : [{'host': '*'}];
corsConfigWithDefaults.allowedMethods = corsConfig.hasOwnProperty('allowedMethods') && Array.isArray(corsConfig.allowedMethods) && corsConfig.allowedMethods.length > 0 ? corsConfig.allowedMethods : DEFAULT_ALLOWED_METHODS;
corsConfigWithDefaults.allowedHeaders = corsConfig.hasOwnProperty('allowedHeaders') && Array.isArray(corsConfig.allowedHeaders) && corsConfig.allowedHeaders.length > 0 ? corsConfig.allowedHeaders : DEFAULT_ALLOWED_HEADERS;
corsConfigWithDefaults.maxAge = corsConfig.hasOwnProperty('maxAge') && corsConfig.maxAge >= 0 ? corsConfig.maxAge : 1800;
corsConfigWithDefaults.allowedCredentials = corsConfig.hasOwnProperty('allowedCredentials') ? corsConfig.allowedCredentials : true;
corsConfigWithDefaults.exposeHeaders = corsConfig.exposeHeaders;
return corsConfigWithDefaults;
}
function send(res, responseHeaders) {
for (let headerName in responseHeaders){
res.setHeader(headerName, responseHeaders[headerName]);
}
res.status = 200;
return res.end();
}

View file

@ -0,0 +1,180 @@
'use strict';
const destinationUtils = require('../../lib/utils/destination-utils');
const tokenUtils = require('../../lib/utils/token-utils');
const jwtDecode = require('jwt-decode');
const expiresAt = require('../passport/utils').getExpiresAt;
const validators = require('../configuration/validators');
const passportUtils = require('../passport/utils');
const sessionExt = require('../utils/session-ext');
const self = module.exports = {
NO_DESTINATION_FLOW: 0,
ENV_DESTINATION_FLOW: 1,
SVC_DESTINATION_FLOW: 2,
retrieveDestination: function (options, cb) {
self.replaceUserToken(options, function (err, userExchangeToken) {
if (err) {
return cb(err);
}
let destinationName = options.destinationName;
let accessToken = userExchangeToken && userExchangeToken.access_token ? userExchangeToken.access_token : userExchangeToken;
destinationUtils.findDestination(destinationName, accessToken, options, function (err, destinationLookUpResult) {
if (err) {
return cb(err);
}
try {
if (!destinationLookUpResult || !destinationLookUpResult.destinationConfiguration) {
return cb('Cannot get destination configuration for destination ' + destinationName);
}
if (destinationLookUpResult.authTokens && destinationLookUpResult.authTokens[0].hasOwnProperty('error')) {
return cb(destinationLookUpResult.authTokens[0].error);
}
if (destinationLookUpResult.authTokens) {
const authTokenExpiresIn = destinationLookUpResult.authTokens[0].expires_in;
destinationLookUpResult.authTokens[0].expireDate = authTokenExpiresIn ? expiresAt(authTokenExpiresIn).getTime() : 0;
}
let result = {};
if (userExchangeToken) {
const tokenDecoded = jwtDecode(userExchangeToken);
const expMs = tokenDecoded.exp * 1000;
const threeMins = 180000;
result.userExchangeToken = {
token: userExchangeToken,
expireDate: expMs > threeMins ? expMs - threeMins : expMs,
destinationKey: options.destinationKey
};
}
let destinationsConfigurations = [destinationLookUpResult.destinationConfiguration];
destinationUtils.normalizeDestinationProperties(destinationsConfigurations);
const normalizedCertificateProperties = destinationUtils.isDestinationCertificatesFlow(destinationsConfigurations[0])
&& destinationUtils.normalizeCertificateProperties(destinationLookUpResult.certificates);
validators.validateDestinations(destinationsConfigurations);
destinationUtils.adjustDestinationProperties(destinationsConfigurations);
result.destination = destinationsConfigurations[0];
result.expireDate = destinationLookUpResult.authTokens && destinationLookUpResult.authTokens[0].expireDate;
if (options.dynamicDestination && !result.destination.dynamicDestination) {
return cb('Destination ' + destinationName + ' is not defined as a dynamic destination in destination service, configure additional property HTML5.DynamicDestination true');
}
result.authToken = destinationLookUpResult.authTokens ? destinationLookUpResult.authTokens[0] : null;
result.destination.certificates = normalizedCertificateProperties && destinationUtils.getDestinationCertificate(destinationsConfigurations[0], normalizedCertificateProperties);
return cb(null, result);
} catch (error) {
return cb(error);
}
});
});
},
getDestinationFlowType: function (internalUrl) {
const destinationName = internalUrl && internalUrl.route && internalUrl.route.destination;
const destinationObj = internalUrl && internalUrl.destination;
if (!destinationName || !destinationObj) {
return self.NO_DESTINATION_FLOW;
}
if (destinationObj.url === 'DESTINATION_URL_PLACEHOLDER') {
return self.SVC_DESTINATION_FLOW;
}
return self.ENV_DESTINATION_FLOW;
},
replaceUserToken: function (options, cb) {
let session = options.session;
if (!session || !session.user || session.user.name === passportUtils.USER_NAME_NOT_APPLICABLE) {
return cb(null);
}
shouldRequestUserExchangeToken(session, options.destinationKey, function (err, askForToken) {
if (err) {
return cb(err);
}
if (!askForToken) {
let userExchangeToken = options.destinationKey && session.user.destinationKey && session.user.destinationKey[options.destinationKey]
? session.user.destinationKey[options.destinationKey].destinationUserExchangeToken.token : session.user.destinationUserExchangeToken.token;
return cb(null, userExchangeToken);
}
getExternalServiceCredentials(options, (err, externalServiceCredentials) => {
if (err) {
return cb('Failed to get external service credentials ' + err);
}
if (!options.session.user.token) {
return cb(`Missing token for session user. correlationId: ${options.correlationId}`);
}
let jwt = options.jwt || options.session.user.token.accessToken;
return tokenUtils.exchangeToken(jwt, options.correlationId, externalServiceCredentials, cb);
});
});
},
addDestinationHeaders: function (req) {
if (req.headers && req.internalUrl && req.internalUrl.destination) {
const escapeHeaders = Object.keys(req.headers);
for (const destinationKey in req.internalUrl.destination) {
if (destinationKey.startsWith('uRL.headers')) {
const headerKey = destinationKey.split('.')[2];
if (headerKey && !escapeHeaders.includes(headerKey)) {
req.headers[headerKey] = req.internalUrl.destination[destinationKey];
}
}
}
}
return req;
},
getIASTokenByIASDependencyName: async function(options) {
const IASDependencyName = (options.internalUrl && options.internalUrl.destination && options.internalUrl.destination.IASDependencyName) ||
options.IASDependencyName;
const loginIdToken = options.session && options.session.user && options.session.user.token && options.session.user.token.idToken;
if (IASDependencyName && loginIdToken && options.zoneInfo){
let token = options.session.user.iasDependencies &&
options.session.user.iasDependencies[IASDependencyName] && options.session.user.iasDependencies[IASDependencyName].token;
const currentTimestamp = Math.floor(Date.now() / 1000);
if (token && token.expiresAt > currentTimestamp){
return token.accessToken;
}
const credentials = options.session.user.token.oauthOptions;
credentials.tokenEndPoint = options.zoneInfo.tokenEndpoint;
credentials.apptid = options.zoneInfo.zoneId;
token = await tokenUtils.getApp2AppToken(loginIdToken, credentials, IASDependencyName, options.correlationId);
// eslint-disable-next-line no-unused-vars
sessionExt.update(options.session, function(session) {
if (!session.user.iasDependencies){
session.user.iasDependencies = {};
}
session.user.iasDependencies[IASDependencyName] = {
token: {
idToken: token.id_token,
accessToken: token.access_token,
expiresAt: token.expires_in + Date.now() - 5
}
};
});
return token.access_token;
}}
};
function shouldRequestUserExchangeToken(session, destinationKey, cb) {
let destinationTokensCache = destinationKey && session.user.destinationKey && session.user.destinationKey[destinationKey]
? session.user.destinationKey[destinationKey].destinationUserExchangeToken : session.user.destinationUserExchangeToken;
// check the expiration of destinationUserExchangeToken
if (!destinationTokensCache || (destinationKey && (destinationKey !== destinationTokensCache.destinationKey)) || (!destinationTokensCache.token || destinationTokensCache.expireDate < Date.now())) {
return cb(null, true);
}
return cb(null, false);
}
function getExternalServiceCredentials(options, cb){
let externalServiceCredentials;
if (options.destinationKey && options.app && options.app.services[options.destinationKey]) {
destinationUtils.getDestinationCredentialsByDestinationKey(options.app, options.destinationKey, options.correlationId, (err, destCredentials) => {
if (err) {
return cb(err);
}
externalServiceCredentials = destCredentials;
externalServiceCredentials.url = externalServiceCredentials.tokenServiceURL;
externalServiceCredentials.clientid = externalServiceCredentials.clientId;
externalServiceCredentials.clientsecret = externalServiceCredentials.clientSecret;
return cb(null, externalServiceCredentials);
});
} else {
externalServiceCredentials = options.destinationCredentials || destinationUtils.getDestinationServiceCredentials();
cb(null, externalServiceCredentials);
}
}

View file

@ -0,0 +1,175 @@
'use strict';
const url = require('url');
const destinationTokenHandler = require('./destination-token-handler');
const sessionExt = require('../utils/session-ext');
const headerUtils = require('../utils/header-util');
const passportUtils = require('../passport/utils');
module.exports = async function (req, res, next) {
const destinationFlowType = destinationTokenHandler.getDestinationFlowType(req.internalUrl);
if (destinationFlowType === destinationTokenHandler.NO_DESTINATION_FLOW) {
return next();
}
const destinationCredentials = req.destinationCredentials && req.destinationCredentials.destination;
const route = req.internalUrl.route;
let options = {
destinationName: route && route.destination,
logger: req.logger,
preferLocal: route && route.preferLocal,
session: req.session,
app: req.app,
dynamicDestination:req.internalUrl.destination.dynamicDestination,
destinationKey: req.destinationKey || (destinationCredentials && destinationCredentials.instanceid),
destinationCredentials: destinationCredentials,
urlTenant: passportUtils.getUrlTenant(req),
correlationId: headerUtils.getCorrelationId(req),
internalUrl: req.internalUrl,
zoneInfo: req.zoneInfo
};
if (destinationFlowType === destinationTokenHandler.ENV_DESTINATION_FLOW){
const err = await addExchangedIASToken(options, req);
return next(err);
}
if (req.routerConfig && req.routerConfig.getToken) {
req.routerConfig.getToken(req, function (err, jwt) {
if (err) {
return next(err);
} else {
options.jwt = jwt;
return getDestination (req, res, options, next);
}
});
} else {
return getDestination (req, res, options, next);
}
};
async function getDestination (req, res, options, next){
const key = options.destinationKey ? options.destinationKey + '-' + options.destinationName : options.destinationName;
let IASExchangeOptions;
let cachedDestination = getCachedDestination(req, key, options.destinationName);
if (cachedDestination){
updateInternalUrl(req,cachedDestination.destinationConfiguration);
IASExchangeOptions = {
IASDependencyName: cachedDestination.destinationConfiguration.iASDependencyName,
zoneInfo: req.zoneInfo,
session: req.session,
correlationId: headerUtils.getCorrelationId(req)
};
const error = await addExchangedIASToken(IASExchangeOptions, req);
if (error) {
return next(error);
}
destinationTokenHandler.addDestinationHeaders(req);
return next();
}
options.correlationId = headerUtils.getCorrelationId(req);
return destinationTokenHandler.retrieveDestination(options, async function (err, result) {
if (err) {
return next(err);
}
updateInternalUrl(req, result.destination); // normalized destination lookup result
destinationTokenHandler.addDestinationHeaders(req);
if (!req.session || !req.session.user){
if (result.authToken){
req.internalUrl.route['authToken'] = result.authToken;
}
return next();
}
IASExchangeOptions = {
IASDependencyName: result.destination.iASDependencyName,
zoneInfo: req.zoneInfo,
session: req.session,
correlationId: headerUtils.getCorrelationId(req)
};
const error = await addExchangedIASToken(IASExchangeOptions, req);
if (error) {
return next(error);
}
sessionExt.update(req.session, function(// eslint-disable-next-line
session) {
if (!req.session.user.destinations){
req.session.user.destinations = {};
}
if (!req.session.user.destinationUserExchangeToken){
req.session.user.destinationUserExchangeToken = {};
}
if (!req.session.user.destinations[key]){
req.session.user.destinations[key] = {};
}
if (result.authToken && !req.session.user.destinations[key].authToken){
req.session.user.destinations[key].authToken = {};
}
if (result.authToken) {
req.session.user.destinations[key].authToken = result.authToken;
}
req.session.user.destinations[key].expireDate = result.expireDate;
req.session.user.destinations[key].destinationConfiguration = result.destination;
if (result.userExchangeToken) {
if (req.destinationKey){
if (!req.session.user.destinationKey){
req.session.user.destinationKey = {};
}
req.session.user.destinationKey[req.destinationKey] = { destinationUserExchangeToken: result.userExchangeToken};
} else {
req.session.user.destinationUserExchangeToken = result.userExchangeToken;
}
}
});
return next();
});
}
function updateInternalUrl (req, destination) {
let destinationUrl = req.internalUrl.destination.url;
let newHref = destination.url;
// Avoid double slash between destination url and path
if (newHref[newHref.length - 1] === '/') {
destinationUrl += '/';
}
req.internalUrl.href = req.internalUrl.href.replace(destinationUrl, newHref);
let newParsedUrl = new url.URL(req.internalUrl.href);
req.internalUrl.protocol = newParsedUrl.protocol;
req.internalUrl.host = newParsedUrl.host;
req.internalUrl.hostname = newParsedUrl.hostname;
req.internalUrl.pathname = newParsedUrl.pathname;
req.internalUrl.path = newParsedUrl.pathname + newParsedUrl.search;
req.internalUrl.port = newParsedUrl.port;
req.internalUrl.destination = destination;
}
function getCachedDestination(req, key, destinationName){
let destination = req.session && req.session.user && req.session.user.destinations && req.session.user.destinations[key];
if (destination && ((destination.destinationConfiguration.authentication === 'BasicAuthentication'
|| destination.destinationConfiguration.authentication === 'NoAuthentication') ||
(destination.expireDate && destination.expireDate - Date.now() > 0))){
const logger = req.loggingContext.getLogger('/Destination service');
logger.info('Destination ' + destinationName + ' has been retrieved from cache');
return destination;
} else {
return null;
}
}
async function addExchangedIASToken(options, req){
try {
const IASToken = await destinationTokenHandler.getIASTokenByIASDependencyName(options);
if (IASToken){
req.internalUrl.route['authToken'] = {
// eslint-disable-next-line camelcase
http_header : {
key: 'Authorization',
value: 'Bearer ' + IASToken
}
};
}
} catch (err){
return err;
}
return null;
}

View file

@ -0,0 +1,76 @@
'use strict';
const http = require('http');
const util = require('util');
const headerUtil = require('../utils/header-util');
const applicationLogUtils = require('../utils/application-logs-utils');
const send = require('send');
const VError = require('verror').VError;
const CONNECTION_ERROR_CODES = {
ECONNRESET: true,
ECONNREFUSED: true
};
module.exports = function errorHandler(err, req, res, next) { // eslint-disable-line no-unused-vars
if (typeof err === 'string') {
err = new Error(err);
}
const status = err.status || 500;
const headers = err.headers || {};
const tracer = req.loggingContext.getTracer(__filename);
const logger = req.loggingContext.getLogger('/Handler');
const errorMessage = req.method + ' request to ' + req.url + ' completed with status ' + status + ' ' + err.message;
logger.error(errorMessage);
applicationLogUtils.logRequestError(req, errorMessage);
tracer.debug((err.stack ? ' stack: ' + err.stack : err));
if (CONNECTION_ERROR_CODES[findErrCode(err)]) {
tracer.info('Check your proxy and network settings.');
}
const routerConfig = req.routerConfig || req.app.get('mainRouterConfig');
const redirectPage = routerConfig.appConfig.errorPage.get(status);
if (redirectPage) {
tracer.debug('Redirecting to error page: ' + redirectPage);
send(req, redirectPage, { index: false, root: routerConfig.workingDir })
.on('error', function (err) {
const verr = new VError(err, 'Unable to fetch custom error page: %s', redirectPage);
endRequest(res, status, headers, verr);
})
.on('directory', function () {
const verr = new VError(err, 'Custom error page is a directory: %s', redirectPage);
endRequest(res, status, headers, verr);
})
.on('headers', function (res) { res.statusCode = status; })
.pipe(res);
return;
}
endRequest(res, status, headers, err);
};
function endRequest(res, status, headers, err) {
if (res.headersSent) {
return;
}
res.setHeader('Cache-Control', headerUtil.NOCACHE_HEADER_VALUE);
res.writeHead(status, headers);
let message = http.STATUS_CODES[status];
if (err && process.env.NODE_ENV === 'development') {
message = util.format('%d: %s\t%s', status, message, err.stack);
}
res.end(message);
}
function findErrCode(err) {
if (!err) {
return;
}
if ('code' in err) {
return err.code;
}
if (typeof err.cause === 'function') {
return findErrCode(err.cause());
}
}

View file

@ -0,0 +1,11 @@
'use strict';
module.exports = function matchRequestHttpMethod(req, res, next) {
let error;
if (Array.isArray(req.supportedHttpMethods)) {
error = new Error('no route matches because of the HTTP method restrictions');
error.status = 405;
error.headers = { allow: req.supportedHttpMethods.join(', ') };
}
next(error);
};

View file

@ -0,0 +1,11 @@
'use strict';
const refreshToken = require('../passport/jwt-refresh').refreshToken;
module.exports = function refreshJwtMiddleware(req, res, next) {
if (!req.session || !req.session.user || req.session.jwtRefreshStarted) {
return next();
}
refreshToken(req.app, req.session);
next();
};

View file

@ -0,0 +1,115 @@
'use strict';
const cookie = require('cookie');
const cookieUtils = require('../utils/cookie-utils');
const urlUtils = require('../utils/url-utils');
const headerUtil = require('../utils/header-util');
const oauthConfig = require('../passport/oauth-configuration.js');
const passport = require('passport');
const url = require('url');
const loginCallbackProvider = require('./login-callback-provider');
const VError = require('verror').VError;
module.exports = function passportLogin(req, res, next) {
if (!loginCallbackProvider.isLoginCallback(req)) {
return next();
}
let errorParam = urlUtils.getQueryParam(req, 'error');
if (errorParam) {
let errorDescriptionParam = urlUtils.getQueryParam(req, 'error_description');
let errorFromQuery = new Error(errorDescriptionParam ? errorParam + ', ' + errorDescriptionParam : errorParam);
return next(errorFromQuery);
}
if (process.env.PRESERVE_FRAGMENT !== 'false') {
req.res = res;
}
oauthConfig.getXSUAAOauthStrategy(req, function (err, strategy) {
if (err) {
return next(err);
}
passport.use(strategy);
passport.authenticate(strategy.name, function (err, user) {
if (err) {
const authType = (req && req.query && req.query.authType) || 'xsuaa';
let error = new VError(err, `Could not authenticate with ${authType}`);
error.status = err.status;
return next(error);
}
if (!user) {
return sendRedirect(res, '/');
}
req.logIn(user,oauthConfig.SESSION_OPTIONS, function (err) {
if (err) {
return next(err);
}
const redirectCookieName = cookieUtils.getRedirectLocationCookieName();
const fragmentCookieName = cookieUtils.getFragmentCookieName();
if (!req.headers || !req.headers.cookie) {
return sendRedirectCookieMissing(next, redirectCookieName);
}
const cookies = cookie.parse(req.headers.cookie);
const redirectCookieValue = cookies[redirectCookieName];
const fragmentCookieValue = cookies[fragmentCookieName];
if (!redirectCookieValue) {
return sendRedirectCookieMissing(next, redirectCookieName);
}
if (!isRelativeUrl(redirectCookieValue)) {
return sendRedirectCookieError(next, 'Redirect path is invalid',
new VError('%s must contain a relative path, got %s', redirectCookieName, redirectCookieValue));
}
let cookieOptions = {
path: '/',
maxAge: 0
};
let locationAfterLogin = cookie.serialize(redirectCookieName, '', cookieOptions);
let samesite = req.routerConfig && req.routerConfig.cookies && req.routerConfig.cookies.SameSite;
cookieUtils.setCookie(res, cookieUtils.addSamesite(locationAfterLogin, samesite));
// Fragment may be empty string, which is casted to false. Therefore check presence of key.
if (cookies.hasOwnProperty(fragmentCookieName)) {
let fragmentAfterLogin = cookie.serialize(fragmentCookieName, '', cookieOptions);
cookieUtils.setCookie(res, cookieUtils.addSamesite(fragmentAfterLogin,samesite));
}
if (cookies.hasOwnProperty('signature')) {
let signature = cookie.serialize('signature', '', cookieOptions);
cookieUtils.setCookie(res, cookieUtils.addSamesite(signature,samesite));
}
sendRedirect(res, normalizeRelativePath(redirectCookieValue + (fragmentCookieValue || '')));
});
})(req, res, next);
});
};
function isRelativeUrl(location) {
const parsedUrl = url.parse(location);
return !(parsedUrl.host || parsedUrl.protocol);
}
function sendRedirectCookieMissing(next, cookieName) {
sendRedirectCookieError(next, 'Missing redirect information', new VError('Required cookie \'%s\' is missing in the request', cookieName));
}
function sendRedirectCookieError(next, message, err) {
const error = new VError(err, 'Unable to redirect after successful authentication. %s.', message);
error.status = 400;
next(error);
}
// normalize path, so it will not lead to unexpecred redirect like '//google.com' for example
function normalizeRelativePath(path) {
return path.trim().replace(/^\/*/, '/');
}
function sendRedirect(res, redirectLocation) {
res.writeHead(302, {
'Location': redirectLocation,
'Cache-Control': headerUtil.NOCACHE_HEADER_VALUE
});
res.end();
}

Some files were not shown because too many files have changed in this diff Show more