@sassoftware/viya-serverjs 0.4.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.env CHANGED
@@ -1,24 +1,38 @@
1
1
  APPHOST=localhost
2
2
  # APPENTRY=index.html
3
3
  APPLOC=./public
4
- # VIYA_SERVER=
5
- # VIYA_SERVER=none
4
+ # VIYA_SERVER=your viya server
5
+
6
6
  APPPORT=8080
7
- APPNAME=viyaapp
8
- AUTHFLOW=server
9
- CLIENTID=viyaapp
7
+ APPNAME=mcpapp
8
+ AUTHFLOW=code
9
+ CLIENTID=mcpapp
10
10
  CLIENTSECRET=jellico
11
+
11
12
  REDIRECT=
12
13
  # APPDIR=./appDir
13
14
  HTTPS=true
14
- VIYA_SERVER=https://viya-00m2kebi2b.engage.sas.com
15
15
 
16
- // browsers do not accept self-signed certs from localhost
16
+
17
+
18
+ # Most modern browsers do not accept self-signed certs from localhost
19
+ # Options:
20
+
21
+ # 1. provide signed certificates for localhost
22
+ # 2. Use libraries like mkcert to create temporary trusted certs for localhost
23
+ # 3. Use the app server as a proxy to the Viya server to avoid CORS issues.
24
+ # This requires that the app redirect all Viya API calls to the app server proxy endpoint
25
+ # Users of restaf can simply set the APPENV_PROXY env to TRUE to enable this behavior
26
+ # 4. set USETOKEN to TRUE and use the token in the APPENV object to make the calls
27
+ // either use the proper ssl/tsl certs or use the "proxy" method
28
+ // to avoid CORS issues with self-signed certs
17
29
  // so run all apps thru the proxy and call Viya from there
30
+ APPENV_PROXY=false
31
+
18
32
 
19
- APPENV_PROXYSERVER=https://localhost:8080/viyaapp/proxy
20
- USETOKEN=YES
21
- SHOWENV=YES
33
+ # APPENV_PROXYSERVER=true
34
+ # USETOKEN=true
35
+ SHOWENV=true
22
36
 
23
37
  APPENV_XYZ=AA
24
38
  APPENV_BAD=
package/.env.server CHANGED
@@ -1,12 +1,14 @@
1
1
  APPHOST=localhost
2
2
  APPENTRY=index.html
3
3
  APPLOC=./public
4
- VIYA_SERVER=
5
- APPENV_PROXYSERVER=https://localhost:8080/appBuilder/proxy
4
+ # viya-server set preset
5
+ # VIYA_SERVER=
6
+ APPENV_PROXYSERVER=https://localhost:8080/viyaapp/proxy
7
+
6
8
  APPPORT=8080
7
- APPNAME=appBuilder
9
+ APPNAME=viyaapp
8
10
  AUTHFLOW=server
9
- CLIENTID=appbuilder
11
+ CLIENTID=viyaapp
10
12
  CLIENTSECRET=jellico
11
13
  REDIRECT=/new
12
14
  # APPDIR=./appDir
package/Dockerfile CHANGED
@@ -11,6 +11,7 @@ ENV APPHOST=0.0.0.0
11
11
  ENV PORT=8080
12
12
  EXPOSE 8080
13
13
  ENV HTTPS=true
14
+ ENV VIYA_SERVER=
14
15
  # ENV APPSERVERLEVEL=v2
15
16
  #######################################################################
16
17
  # You can override these(but in container leave APPHOST as shown below)
@@ -20,20 +21,19 @@ ENV HTTPS=true
20
21
  # ENV APPPORT=8080
21
22
 
22
23
  ENV APPNAME=viyaapp
23
- # ENV AUTHFLOW=
24
+ ENV AUTHFLOW=server
24
25
  ENV CLIENTID=viyaapp
25
- ENV CLIENTSECRET=jellico
26
+ ENV CLIENTSECRET=
26
27
  # ENV HAPIDEBUG=NO
27
28
  # ENV LOGLEVEL=info
28
29
  # ENV USETOKEN=YES
29
30
 
31
+ # specify ssl/tls cert and key in a folder
32
+ # example below
33
+ ENV SSLCERT=c:/Users/kumar/.tls
30
34
  #sample setup for creating a temporary cert and key
31
35
  ENV TLS_CREATE="C:US,ST:NC,L:Cary,O:SAS Institute,OU:STO,CN:localhost"
32
36
 
33
- # You can specify your own cet and key
34
- # ENV TLS_CRT=./tls/tls.crt
35
- # ENV TLS_KEY=./tls/tls.key
36
-
37
37
  # Samesite specification
38
38
  ENV SAMESITE=None,secure
39
39
 
package/README.md CHANGED
@@ -1,99 +1,67 @@
1
- # `Application servers for use with SAS Viya`
1
+ # `Application server for use with SAS Viya`
2
2
 
3
- This package has two servers:
3
+ viya-serverjs is a app server designed to support user written SAS Viya applications. The applications can be written using any framework.
4
4
 
5
- 1. viya-appserverjs - Use this for developing an app server for web applications(see packages/appjs)
5
+ Key features:
6
6
 
7
- 2. viya-apiserverjs - Use this to develop rest api servers(see packages/apijs)
7
+ 1. Handles authentication
8
+ 2. Extendable with additional end points
8
9
 
9
10
  ## Usage
10
11
 
11
- Specify it as a dependency in your package.json just as you do with other dependencies
12
-
13
12
  Use npx command to start the server
14
13
 
15
14
  ```sh
16
- npx @sassoftware/viyaappserverjs
15
+ npx @sassoftware/viya-serverjs
17
16
  ```
18
17
 
19
18
  ## `Basic configuration`
20
19
 
21
- 1. Set the default settings in Dockerfile. This will ensure these are set when you build containers.
22
- 2. The defaults can be overriden using environment variables.
20
+ Configure the server using a .env file
23
21
 
24
22
  ### `Sample env file`
25
23
 
26
- When running on a non-docker environment, you can use a .env
24
+ >[Note] Sample values shown below. See Advanced section for other ways to configure the server
27
25
 
28
26
  ```env
29
- VIYA_SERVER=<your viya server>
30
- APPHOST=localhost < can also be dns name of your server. ex: viyaiscool.unx.sas.com>
31
- APPPORT=5000 <any port of your choice>
32
- APPNAME=viyaapp
33
-
34
- CLIENTID=viyaapp
35
- CLIENTSECRET=secret
36
- ```
37
-
38
- ### `Sample Dockerfile`
39
27
 
40
- ```env
41
- FROM node:12.16.1-alpine
42
- LABEL maintainer="your email"
43
- WORKDIR /usr/src/app
44
- COPY . .
45
- RUN npm install
46
- # RUN npm run build (if you have to build something)
47
- EXPOSE 8080
48
- ENV APPHOST=0.0.0.0
28
+ # Base setup
29
+ # With the configuration below the app server url will be
30
+ # https://localhost:8080/viyaapp
31
+ #
32
+ APPHOST=localhost
33
+ APPPORT=8080
34
+ HTTPS=true
35
+ APPNAME=viyaapp
49
36
 
50
- AUTHFLOW=code
37
+ # Most modern browsers will reject self-signed certs from localhost
38
+ # And SAS Viya might also refuse connection.
39
+ # Supply your own SSL/TLS values in a folder. All files in this folder will be used.
40
+ # Options:
41
+ # 1. provide signed certificates for localhost
42
+ # 2. Use libraries like mkcert to create temporary trusted certs for localhost
43
+ # 3. For other options see the Advanced Section
44
+ SSLCERT=./tls
51
45
 
52
- # The following are defaults. Override them as needed
53
- # APPLOC - where the file specified in APPENTRY is
54
- # APPENTRY - the main entry of the application
55
- ENV APPLOC=./public
56
- ENV APPENTRY=index.html
57
- # if your app takes advantage of appenv.js to pass configuration to the web application
58
- # ENV APPENV=appenv.js
46
+ # AUTHENTICATION
47
+ VIYA_SERVER=<viya servrer url>
59
48
 
60
- # See notes below on running with SSL enabled
61
- ENV TLS_CREATE="C:US,ST:NC,L:Cary,O:yourcompany,OU:STO,CN:localhost"
62
- ENV SAMESITE=None,secure
49
+ # By default it uses authorization_code flow
63
50
 
64
- # It is better to set this before invoking the server
65
- ENV NODE_TLS_REJECT_UNAUTHORIZED=0
51
+ CLIENTID=<clientid>
52
+ CLIENTSECRET=<clientSecret if present>
53
+ AUTHFLOW=code|pkce
66
54
 
67
- # set this to YES if you want access to the authentication token in the app
68
- ENV USETOKEN=NO
55
+ ##########################
56
+ # Read the Advanced Section in the README before turning on these options
57
+ #
58
+ APPENV_PROXY=false
59
+ USETOKEN=false
69
60
 
70
- CMD ["npx", "@sassoftware/viya-appserverjs"]
61
+ APPENV_A=somevalue
62
+ APPENV_B=somevalue
71
63
 
72
64
  ```
73
65
 
74
- ### `Running with SSL enabled -- Recommended`
75
-
76
- This is the recommended setting. This will also make browsers like Chrome run with the SAMESITE settings set to Default - your users will thank you.
77
-
78
- Make sure you specify the VIYA_SERVER with a protocol of https.
79
66
 
80
- ### `TLS certificates`
81
67
 
82
- - Option 1: Let server create a temporary unsigned certificate
83
-
84
- ```env
85
- ENV TLS_CREATE=C:US,ST:NC,L:Cary,O:YourCompany,OU:yourgroup,CN:localhost
86
- ```
87
-
88
- - Option 2: Provide your own key and certificate key
89
-
90
- ```env
91
- ENV TLS_KEY=../certs/self/key.pem
92
- ENV TLS_CERT=../certs/self/certificate.pem
93
- ```
94
-
95
- - Option 3: Provide key and certificate as a pfx file
96
-
97
- ```env
98
- ENV TLS_PFX=../certs/sascert/sascert2.pfx
99
- ```
@@ -25,7 +25,6 @@ function _setCookies() {
25
25
  case 0:
26
26
  debugger;
27
27
  credentials = req.auth.credentials;
28
- console.log('setcookies credentials', credentials);
29
28
  debug('setcookie', credentials);
30
29
  if (!(credentials != null && req.auth.error != null)) {
31
30
  _context.n = 1;
package/lib/iService.js CHANGED
@@ -181,6 +181,7 @@ function iService(userRouteTable, useDefault, asset, allAppEnv, serverMode, user
181
181
  },
182
182
  });
183
183
  */
184
+ //
184
185
  // setup authentication related plugins
185
186
  options = {
186
187
  serverMode: serverMode,
@@ -193,6 +194,7 @@ function iService(userRouteTable, useDefault, asset, allAppEnv, serverMode, user
193
194
  redirect: process.env.REDIRECT,
194
195
  clientId: process.env.CLIENTID,
195
196
  clientSecret: process.env.CLIENTSECRET,
197
+ pkce: allAppEnv.LOGONPAYLOAD.pkce,
196
198
  redirectTo: "/".concat(process.env.APPNAME, "/logon"),
197
199
  allAppEnv: allAppEnv,
198
200
  useHapiCookie: true,
@@ -320,22 +322,8 @@ function _getCertificates() {
320
322
  _context2.n = 1;
321
323
  break;
322
324
  }
323
- console.log('ssl CERTIFICATES', tlsdir);
324
- if (fs.existsSync("".concat(tlsdir, "/key.pem")) === true) {
325
- options = {};
326
- options.key = fs.readFileSync("".concat(tlsdir, "/key.pem"), {
327
- encoding: 'utf8'
328
- });
329
- options.cert = fs.readFileSync("".concat(tlsdir, "/crt.pem"), {
330
- encoding: 'utf8'
331
- });
332
- if (fs.existsSync("".concat(tlsdir, "/ca.pem")) === true) {
333
- options.ca = fs.readFileSync("".concat(tlsdir, "/ca.pem"), {
334
- encoding: 'utf8'
335
- });
336
- }
337
- options.rejectUnauthorized = true;
338
- }
325
+ options = readTLS(tlsdir);
326
+ options.rejectUnauthorized = true;
339
327
  _context2.n = 3;
340
328
  break;
341
329
  case 1:
@@ -352,6 +340,26 @@ function _getCertificates() {
352
340
  }));
353
341
  return _getCertificates.apply(this, arguments);
354
342
  }
343
+ function readTLS(tlsdir) {
344
+ console.log("[Note] Using TLS dir: " + tlsdir);
345
+ if (fs.existsSync(tlsdir) === false) {
346
+ console.log("[Warning] Specified TLS dir does not exist: " + tlsdir);
347
+ return null;
348
+ }
349
+ var listOfFiles = fs.readdirSync(tlsdir);
350
+ console.log("[Note] TLS/SSL files found: " + listOfFiles);
351
+ var options = {};
352
+ for (var i = 0; i < listOfFiles.length; i++) {
353
+ var fname = listOfFiles[i];
354
+ var name = tlsdir + '/' + listOfFiles[i];
355
+ var key = fname.split('.')[0];
356
+ options[key] = fs.readFileSync(name, {
357
+ encoding: 'utf8'
358
+ });
359
+ }
360
+ console.log('TLS FILES', Object.keys(options));
361
+ return options;
362
+ }
355
363
  function getTls() {
356
364
  return _getTls.apply(this, arguments);
357
365
  }
package/lib/index.js CHANGED
@@ -29,7 +29,7 @@ function _interopRequireDefault(e) { return e && e.__esModule ? e : { "default":
29
29
  var debug = require("debug")("startup");
30
30
  module.exports = function core(uTable, useDefault, serverMode, customize, swaggerfcn) {
31
31
  var argv = (0, _yargs["default"])((0, _helpers.hideBin)(process.argv)).argv;
32
- var env = argv.env == null ? null : argv.env;
32
+ var env = argv.env == null ? '.env' : argv.env;
33
33
  var appenv = argv.appenv == null ? null : argv.appenv;
34
34
  var docker = argv.docker == null ? null : argv.docker;
35
35
  //process.env.SERVERMODE = serverMode !== null ? "api" : "app";
@@ -95,20 +95,11 @@ function getAllEnv(userData) {
95
95
  console.log('Note: setting host to null');
96
96
  host = null;
97
97
  }
98
-
99
- /*
100
- if (process.env.AUTHTYPE != null) {
101
- process.env.AUTHFLOW = process.env.AUTHTYPE;
102
- }
103
- */
104
-
105
98
  var authflow = trimit("AUTHFLOW");
106
- if (authflow === "authorization_code" || authflow === "code") {
99
+ var pkce = authflow === "pkce" ? true : false;
100
+ if (authflow === "authorization_code" || authflow === "code" || authflow === "server" || authflow === "null" || authflow === "pkce") {
107
101
  authflow = "server";
108
102
  }
109
- if (authflow === null) {
110
- host = null;
111
- }
112
103
  if (host === null) {
113
104
  authflow = null;
114
105
  console.log('Note: setting authflow to null');
@@ -119,7 +110,7 @@ function getAllEnv(userData) {
119
110
  var clientID = trimit("CLIENTID");
120
111
 
121
112
  // eslint-disable-next-line no-unused-vars
122
- var clientSecret = trimit("CLIENTSECRET");
113
+ //let clientSecret = trimit("CLIENTSECRET");
123
114
  var keepAlive = trimit("KEEPALIVE");
124
115
  var appName = trimit("APPNAME");
125
116
  var ns = trimit("NAMESPACE");
@@ -130,6 +121,7 @@ function getAllEnv(userData) {
130
121
  host: host,
131
122
  clientID: clientID,
132
123
  appName: appName,
124
+ pkce: pkce,
133
125
  keepAlive: null,
134
126
  useToken: process.env.USETOKEN,
135
127
  ns: ns,
@@ -186,6 +178,7 @@ function getAllEnv(userData) {
186
178
  }
187
179
  }
188
180
  }
181
+ userData.APPNAME = l.appName;
189
182
  env = {
190
183
  LOGONPAYLOAD: l,
191
184
  APPENV: userData
@@ -79,11 +79,13 @@ function _iSASauth() {
79
79
  provider: provider,
80
80
  password: uuid.v4(),
81
81
  clientId: options.clientId,
82
- clientSecret: options.clientSecret,
82
+ clientSecret: options.clientSecret == null ? '' : options.clientSecret,
83
83
  // isSameSite : options.isSameSite,
84
84
  isSecure: options.isSecure
85
85
  };
86
- // console.log('SASAuth options', bellAuthOptions);
86
+ if (options.pkce === true) {
87
+ bellAuthOptions.pkce = 'S256';
88
+ }
87
89
  debug('belloptions', bellAuthOptions);
88
90
  server.log('SASAuth', bellAuthOptions);
89
91
  _context2.n = 1;
@@ -51,7 +51,6 @@ module.exports = /*#__PURE__*/function () {
51
51
  } else {
52
52
  sid = session.sid;
53
53
  }
54
- console.log('appcookie sid', sid);
55
54
  if (!(sid != null)) {
56
55
  _context.n = 3;
57
56
  break;
@@ -2,6 +2,8 @@
2
2
 
3
3
  function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
4
4
  var _handlers = require("../handlers");
5
+ var _setContext = _interopRequireDefault(require("./setContext"));
6
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { "default": e }; }
5
7
  function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
6
8
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
7
9
  function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
@@ -43,7 +45,7 @@ module.exports = function setDefaultRoutes(server, options) {
43
45
  strategy: "sas"
44
46
  };
45
47
  }
46
- var getAppb = _handlers.getApp.bind(null, process.env.USETOKEN === "YES" ? options : null);
48
+ var getAppb = _handlers.getApp.bind(null, process.env.USETOKEN != null && process.env.USETOKEN.toUpperCase() === "TRUE" ? options : null);
47
49
  console.log("Default strategy", authDefault);
48
50
  console.log("Logon strategy", authLogon);
49
51
  options.authDefault = authDefault;
@@ -271,6 +273,13 @@ module.exports = function setDefaultRoutes(server, options) {
271
273
  };
272
274
  debug(pr);
273
275
  defaultTable.push(pr);
276
+ // now set pre for all default routes
277
+ defaultTable.forEach(function (r) {
278
+ r.options.pre = [{
279
+ method: _setContext["default"],
280
+ assign: 'context'
281
+ }];
282
+ });
274
283
  var routeTables = uTable !== null ? defaultTable.concat(uTable) : defaultTable;
275
284
  server.route(routeTables);
276
285
  };
@@ -51,11 +51,13 @@ function setupUserRoutes(u, options) {
51
51
  assign: 'context'
52
52
  }]);
53
53
  }
54
+ console.log(rx.options.pre);
54
55
  if (rx.options.auth === true) {
55
56
  rx.options.auth = options.authDefault;
56
57
  } else if (rx.options.auth === 'logon') {
57
58
  rx.options.auth = options.authLogon;
58
59
  }
60
+ console.log('route auth', rx.options.auth);
59
61
  return rx;
60
62
  });
61
63
  return routes;
package/mcpServer.js ADDED
@@ -0,0 +1,364 @@
1
+ /*
2
+ * Copyright © 2025, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+ let core = require('./lib/index.js');
6
+ debugger;
7
+ core(getCustomHandler, true, 'app', null);
8
+
9
+ function getCustomHandler() {
10
+ let appName = `/${process.env.APPNAME}`; /* does not have to be this - your choice */
11
+ debugger;
12
+ let routes = [
13
+ {
14
+ method: ["GET"],
15
+ path: "/help",
16
+ options: {
17
+ files: {
18
+ relativeTo: "./public",
19
+ },
20
+ handler: async (req, h) => {
21
+ debugger;
22
+ let hf = 'help.html';
23
+ return h.file(hf);
24
+ },
25
+ auth: false,
26
+ description: "Help",
27
+ notes: "Help",
28
+ tags: ["app"],
29
+ },
30
+ },
31
+ {
32
+ method: ["GET"],
33
+ path: `${appName}/new`,
34
+ options: {
35
+ files: {
36
+ relativeTo: "./public",
37
+ },
38
+ handler: async (req, h) => {
39
+ console.log('>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>in new');
40
+ return h.file('index.html');
41
+ },
42
+ // auth: 'logon',
43
+ description: "Create new application",
44
+ notes: "Index file created from env data",
45
+ tags: ["app"],
46
+ },
47
+ }
48
+
49
+ ];
50
+ return routes;
51
+ }
52
+ function customize(key, options) {
53
+ let info = {
54
+ swaggerOptions: {
55
+ info: {
56
+ title: "Test API",
57
+ version: "0.0.1",
58
+ description: "This document was auto-generated at run time",
59
+ },
60
+ documentationPage: true,
61
+ documentationPath: `/${process.env.APPNAME}/documentation`,
62
+ swaggerUI: true,
63
+ swaggerUIPath: `/${process.env.APPNAME}/swaggerui`,
64
+ schemes: ["https", "http"],
65
+ cors: true,
66
+ auth: options.authDefault,
67
+ },
68
+ APPENV: {
69
+ x: 1,
70
+ y: 2,
71
+ },
72
+ };
73
+ let r = info[key];
74
+ return r == null ? {} : r;
75
+ }
76
+
77
+ function getIndex() {
78
+
79
+ let template = `
80
+ <html lang="en">
81
+ <head>
82
+ <meta charset="UTF-8" />
83
+
84
+ <script
85
+ crossorigin
86
+ src="https://unpkg.com/react@16/umd/react.production.min.js"
87
+ ></script>
88
+ <script
89
+ crossorigin
90
+ src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"
91
+ ></script>
92
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.26.0/babel.min.js"></script>
93
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-polyfill/6.26.0/polyfill.min.js"></script>
94
+ <script src="https://unpkg.com/@sassoftware/restaf@5.2.4/dist/restaf.js"></script>
95
+ <script src="https://unpkg.com/@sassoftware/restaflib@5.2.4/dist/restaflib.js"></script>
96
+
97
+ <style>
98
+ .container {
99
+ display: flex;
100
+ flex-direction: column;
101
+ flex-wrap: nowrap;
102
+ min-height: 800px;
103
+ }
104
+ .elabel {
105
+ display: inline-block;
106
+
107
+ clear: left;
108
+ width: 250px;
109
+ text-align: right;
110
+ }
111
+ .einput {
112
+ display: inline-block;
113
+ }
114
+ .div1 {
115
+ border: 1px solid black;
116
+ background: lightskyblue;
117
+ }
118
+ .div2 {
119
+ border: 1px solid black;
120
+ background: lightskyblue;
121
+ height: 200px;
122
+ }
123
+ </style>
124
+
125
+ <script>
126
+ let LOGONPAYLOAD = {
127
+ host: "${process.env.VIYA_SERVER}",
128
+ authType: "server",
129
+ appName: "${process.env.APPNAME}",
130
+ };
131
+ </script>
132
+
133
+ <script>
134
+ debugger;
135
+ let store = restaf.initStore({
136
+ casProxy: true});
137
+ debugger; console.log(store.config);
138
+
139
+ let session = null;
140
+ let servers = null;
141
+ let services = null;
142
+ let files = null;
143
+ let reports = null;
144
+ let compute = null;
145
+
146
+ function setup() {
147
+ debugger;
148
+ document.getElementById('output').innerHTML = '...initializing';
149
+
150
+ initSession()
151
+ .then(r => {
152
+ document.getElementById('output').innerHTML = 'ready';
153
+ keepAlive();
154
+ })
155
+ .catch(e => {
156
+
157
+ console.log(e);
158
+ });
159
+ }
160
+
161
+
162
+ async function initSession() {
163
+
164
+ console.log(LOGONPAYLOAD);
165
+ debugger;
166
+ let msg = await store.logon(LOGONPAYLOAD);
167
+ console.log(msg);
168
+
169
+ // let { identities } = await store.addServices('identities');
170
+ let name = 'user';
171
+ // if (identities.links('currentUser') != null) {
172
+ // let c = await store.apiCall(identities.links('currentUser'));
173
+ // name = c.items('id');
174
+ // }
175
+ console.log(name);
176
+ debugger;
177
+ /*
178
+ services = await store.addServices(
179
+ 'files', 'compute', 'casManagement'
180
+ );
181
+ console.log(services.casManagement.links().toJS())
182
+ */
183
+ debugger;
184
+ return 'done';
185
+ }
186
+ function runit(type) {
187
+
188
+
189
+ document.getElementById('output').innerHTML = '...running';
190
+ let testcase;
191
+ switch (type) {
192
+ case 'files': {
193
+ testcase = SASfileService;
194
+ break;
195
+ }
196
+ case 'compute': {
197
+ testcase = dsCompute;
198
+ break;
199
+ }
200
+ case 'cas': {
201
+ testcase = runCas;
202
+ break;
203
+ }
204
+
205
+ case 'spre': {
206
+ testcase= spre;
207
+
208
+ break;
209
+ }
210
+ default: {
211
+ testcase = SASfileService;
212
+ break;
213
+ }
214
+ }
215
+
216
+ testcase(store)
217
+ .then(r => {
218
+ document.getElementById(
219
+ 'output'
220
+ ).innerHTML = JSON.stringify(r, null, 4);
221
+ })
222
+ .catch(err => {
223
+
224
+ document.getElementById(
225
+ 'output'
226
+ ).innerHTML = JSON.stringify(err, null, 4);
227
+ });
228
+ }
229
+ async function noaction() {
230
+ r = {msg: 'redirects completed'};
231
+ return r;
232
+ }
233
+ async function spre(store) {
234
+ let p = {
235
+ method: 'GET',
236
+ url : 'http://localhost:3000/api/test',
237
+ withCredentials: true
238
+ }
239
+ let r = await store.request(p);
240
+ return r.data;
241
+ }
242
+ async function runCas(store) {
243
+
244
+ let {casManagement} = await store.addServices('casManagement');
245
+ let servers = await store.apiCall(
246
+ casManagement.links('servers')
247
+ );
248
+ let serverName = servers.itemsList(0);
249
+ let session = await store.apiCall(
250
+ servers.itemsCmd(serverName, 'createSession')
251
+ );
252
+ let payload = {
253
+ action: 'builtins.echo',
254
+ data: { code: { x: 1 } }
255
+ };
256
+ console.log(JSON.stringify(session.links("execute"), null, 4));
257
+ let r = await store.runAction(session, payload);
258
+ console.log('echo completed');
259
+ await store.apiCall(session.links('delete'));
260
+ return r.items();
261
+ }
262
+
263
+ async function SASfileService(store) {
264
+ debugger;
265
+ let content;
266
+ try {
267
+ debugger;
268
+ let {files} = await store.addServices('files');
269
+ debugger;
270
+ console.log(JSON.stringify(files.links(), null, 4));
271
+ //console.log('items - should be an array of files(empty array is ok)')
272
+ // console.log(files.items().toJS());
273
+ let payload = {
274
+ data: { x: 1, y: 'This was saved earlier in the step' },
275
+ headers: { 'content-type': 'application/json' }
276
+ };
277
+ let createCmd = files.links('create');
278
+ let newFile = await store.apiCall(createCmd, payload);
279
+ debugger;
280
+ console.log(JSON.stringify(newFile.links('content'), null, 4));
281
+ content = await store.apiCall(newFile.links('content'));
282
+
283
+ } catch(err) {
284
+ console.log(JSON.stringify(err, null, 4));
285
+ debugger;
286
+ }
287
+ console.log(content);
288
+ return content.items();
289
+ }
290
+ async function dsCompute(store) {
291
+ let log = null;
292
+ debugger;
293
+ let {compute} = await store.addServices('compute');
294
+ let servers = await store.apiCall(compute.links('servers'));
295
+
296
+ let contexts = await store.apiCall(compute.links('contexts'));
297
+
298
+ // lookup the name of the first context and then use it to get the associated createSession restafLink
299
+ let createSession = contexts.itemsCmd(
300
+ contexts.itemsList(0),
301
+ 'createSession'
302
+ );
303
+ let session = await store.apiCall(createSession);
304
+
305
+ // Now run a simple data step in that session
306
+ let payload = {
307
+ data: {
308
+ code: ["data _null_; do i = 1 to 100; x=1; end; run; "]
309
+ }
310
+ };
311
+
312
+ // Now execute the data step and wait for completion
313
+ let job = await store.apiCall(
314
+ session.links('execute'),
315
+ payload
316
+ );
317
+ let status = await store.jobState(job, null, 5, 2);
318
+
319
+ if (status.data === 'running') {
320
+ throw "ERROR: Job did not complete in allotted time";
321
+ } else {
322
+ switch (status.data) {
323
+ case 'warning':
324
+ console.log("Warning: check your log for warnings");
325
+ break;
326
+ case 'error':
327
+ throw "Please correct errors and rerun program";
328
+ default:
329
+ log = await store.apiCall(status.job.links('log'));
330
+ break;
331
+ }
332
+ }
333
+ return log === null ? status : log.items();
334
+ }
335
+ </script>
336
+ </head>
337
+ <body onload="setup()">
338
+ <h1 id="head">Hi</h1>
339
+ <div>
340
+
341
+ <button onclick="runit('files')">
342
+ Press to make a call to file service
343
+ </button>
344
+ <br />
345
+ <br />
346
+ <button onclick="runit('compute')">
347
+ Press to make a call compute service
348
+ </button>
349
+ <br />
350
+ <br />
351
+ <button onclick="runit('cas')">
352
+ Press to make a call to cas echo
353
+ </button>
354
+ <br />
355
+ <br />
356
+
357
+ <div>
358
+ <pre id="output"></pre>
359
+ </div>
360
+ </body>
361
+ </html>
362
+ `;
363
+ return template;
364
+ }
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@sassoftware/viya-serverjs",
3
- "version": "0.4.0",
3
+ "version": "0.5.1",
4
4
  "description": "Easy to use app server for SAS Viya applications",
5
5
  "author": "Deva Kumaraswamy <deva.kumar@sas.com>",
6
6
  "license": "Apache-2.0",
7
7
  "main": "./lib/index.js",
8
8
  "bin": {
9
- "@sassoftware/viya-appserverjs": "cli.js"
9
+ "@sassoftware/viya-serverjs": "cli.js"
10
10
  },
11
11
  "keywords": [
12
12
  "restaf",
@@ -32,6 +32,7 @@
32
32
  "test": "node cli --env=./.env --docker=./Dockerfile",
33
33
  "submit": "cross-env NODE_TLS_REJECT_UNAUTHORIZED=0 APPENTRY=simplesubmit.html node cli --env=./.env --docker=./Dockerfile",
34
34
  "server": "cross-env NODE_TLS_REJECT_UNAUTHORIZED=0 node server.js --env=./.env.server --docker=./Dockerfile",
35
+ "server2": "cross-env NODE_TLS_REJECT_UNAUTHORIZED=0 node server.js --env=./.env --docker=./Dockerfile",
35
36
  "debug": "cross-env NODE_TLS_REJECT_UNAUTHORIZED=0 node --inspect-brk server.js --env=./.env.server --docker=./Dockerfile",
36
37
  "proxy": "cross-env NODE_TLS_REJECT_UNAUTHORIZED=0 node cli --env=./.env.proxy --docker=./Dockerfile",
37
38
  "pub": "npm publish --tag dev --access public",
package/public/help.html CHANGED
@@ -1 +1 @@
1
- <h1> Hellp</h1>
1
+ <h1> Hello</h1>
package/public/index.html CHANGED
@@ -66,10 +66,16 @@
66
66
  <script>
67
67
  debugger;
68
68
  console.log(JSON.stringify(APPENV, null, 4));
69
+ let proxyServer = null;
70
+ if (APPENV.PROXY.toUpperCase() === 'TRUE') {
71
+ proxyServer = `${window.location.protocol}//${window.location.host}/${LOGONPAYLOAD.appName}/proxy`;
72
+
73
+ }
74
+ console.log('Proxy Server = ' + proxyServer);
69
75
  let store = restaf.initStore({
70
76
  casProxy: true,
71
77
  options: {
72
- proxyServer: APPENV.PROXYSERVER,
78
+ proxyServer: proxyServer,
73
79
  httpOptions: null
74
80
  }
75
81
  });
package/server.js CHANGED
@@ -22,7 +22,7 @@ function getCustomHandler() {
22
22
  let hf = 'help.html';
23
23
  return h.file(hf);
24
24
  },
25
- auth: false,
25
+ auth: true,
26
26
  description: "Help",
27
27
  notes: "Help",
28
28
  tags: ["app"],
@@ -30,21 +30,25 @@ function getCustomHandler() {
30
30
  },
31
31
  {
32
32
  method: ["GET"],
33
- path: `${appName}/new`,
33
+ path: `/mcp`,
34
34
  options: {
35
35
  files: {
36
36
  relativeTo: "./public",
37
37
  },
38
38
  handler: async (req, h) => {
39
- console.log('>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>in new');
39
+ console.log('>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>in mcp');
40
+ console.log(req.auth.credentials);
41
+ debugger;
42
+ console.log(req.context);
40
43
  return h.file('index.html');
41
44
  },
42
- // auth: 'logon',
45
+ auth: 'logon',
43
46
  description: "Create new application",
44
47
  notes: "Index file created from env data",
45
48
  tags: ["app"],
46
49
  },
47
50
  }
51
+
48
52
  ];
49
53
  return routes;
50
54
  }
@@ -8,7 +8,6 @@ let debug = require('debug')('setcookies');
8
8
  async function setCookies (req, h, options) {
9
9
  debugger;
10
10
  let credentials = req.auth.credentials;
11
- console.log('setcookies credentials', credentials);
12
11
  debug('setcookie', credentials);
13
12
 
14
13
  if (credentials != null && req.auth.error != null) {
package/src/iService.js CHANGED
@@ -152,6 +152,7 @@ function iService (userRouteTable, useDefault, asset, allAppEnv, serverMode, use
152
152
  });
153
153
  */
154
154
 
155
+ //
155
156
  // setup authentication related plugins
156
157
 
157
158
  let options = {
@@ -165,6 +166,7 @@ function iService (userRouteTable, useDefault, asset, allAppEnv, serverMode, use
165
166
  redirect : process.env.REDIRECT,
166
167
  clientId : process.env.CLIENTID,
167
168
  clientSecret : process.env.CLIENTSECRET,
169
+ pkce : allAppEnv.LOGONPAYLOAD.pkce,
168
170
  redirectTo : `/${process.env.APPNAME}/logon`,
169
171
  allAppEnv : allAppEnv,
170
172
  useHapiCookie : true,
@@ -176,7 +178,7 @@ function iService (userRouteTable, useDefault, asset, allAppEnv, serverMode, use
176
178
  userInfo : userInfo,
177
179
  https : process.env.HTTPS,
178
180
  authDefault : false, /* set later in setDefaultRoutes */
179
- authLogon : false /* set later in setDefaultRoutes */
181
+ authLogon : false /* set later in setDefaultRoutes */
180
182
 
181
183
  };
182
184
 
@@ -263,16 +265,8 @@ async function getCertificates () {
263
265
  let options = null;
264
266
  let tlsdir = process.env.SSLCERT;
265
267
  if (tlsdir != null && tlsdir.trim().length > 0) {
266
- console.log('ssl CERTIFICATES', tlsdir);
267
- if (fs.existsSync(`${tlsdir}/key.pem`) === true) {
268
- options = {};
269
- options.key = fs.readFileSync(`${tlsdir}/key.pem`, { encoding: 'utf8' });
270
- options.cert = fs.readFileSync(`${tlsdir}/crt.pem`, { encoding: 'utf8' });
271
- if (fs.existsSync(`${tlsdir}/ca.pem`) === true) {
272
- options.ca = fs.readFileSync(`${tlsdir}/ca.pem`, { encoding: 'utf8' });
273
- }
274
- options.rejectUnauthorized= true;
275
- }
268
+ options = readTLS(tlsdir);
269
+ options.rejectUnauthorized= true;
276
270
  } else {
277
271
  console.log('No SSL certificates found, generating self-signed certificates');
278
272
  options = await getTls();
@@ -281,6 +275,27 @@ async function getCertificates () {
281
275
  return options;
282
276
  }
283
277
 
278
+ function readTLS (tlsdir) {
279
+ console.log("[Note] Using TLS dir: " + tlsdir);
280
+ if (fs.existsSync(tlsdir) === false) {
281
+ console.log("[Warning] Specified TLS dir does not exist: " + tlsdir);
282
+ return null;
283
+ }
284
+
285
+ let listOfFiles = fs.readdirSync(tlsdir);
286
+ console.log("[Note] TLS/SSL files found: " + listOfFiles);
287
+ let options = {};
288
+ for(let i=0; i < listOfFiles.length; i++) {
289
+ let fname = listOfFiles[i];
290
+ let name = tlsdir + '/' + listOfFiles[i];
291
+ let key = fname.split('.')[0];
292
+ options[key] = fs.readFileSync(name, { encoding: 'utf8' });
293
+ }
294
+ console.log('TLS FILES', Object.keys(options));
295
+ return options;
296
+
297
+ }
298
+
284
299
  async function getTls () {
285
300
  let options = {
286
301
  keySize : 2048,
package/src/index.js CHANGED
@@ -33,7 +33,7 @@ module.exports = function core(
33
33
  swaggerfcn
34
34
  ) {
35
35
  let argv = yargs(hideBin(process.argv)).argv;
36
- let env = argv.env == null ? null : argv.env;
36
+ let env = argv.env == null ? '.env' : argv.env;
37
37
  let appenv = argv.appenv == null ? null : argv.appenv;
38
38
  let docker = argv.docker == null ? null : argv.docker;
39
39
  //process.env.SERVERMODE = serverMode !== null ? "api" : "app";
@@ -124,20 +124,15 @@ function getAllEnv(userData) {
124
124
  host = null;
125
125
  }
126
126
 
127
- /*
128
- if (process.env.AUTHTYPE != null) {
129
- process.env.AUTHFLOW = process.env.AUTHTYPE;
130
- }
131
- */
132
127
 
133
128
  let authflow = trimit("AUTHFLOW");
134
- if (authflow === "authorization_code" || authflow === "code") {
129
+ let pkce = (authflow === "pkce") ? true : false;
130
+ if (authflow === "authorization_code" || authflow === "code" || authflow === "server" ||
131
+ authflow === "null" || authflow === "pkce") {
135
132
  authflow = "server";
133
+
136
134
  }
137
135
 
138
- if (authflow === null) {
139
- host = null;
140
- }
141
136
 
142
137
  if (host === null) {
143
138
  authflow = null;
@@ -151,7 +146,7 @@ function getAllEnv(userData) {
151
146
  let clientID = trimit("CLIENTID");
152
147
 
153
148
  // eslint-disable-next-line no-unused-vars
154
- let clientSecret = trimit("CLIENTSECRET");
149
+ //let clientSecret = trimit("CLIENTSECRET");
155
150
  let keepAlive = trimit("KEEPALIVE");
156
151
  let appName = trimit("APPNAME");
157
152
  let ns = trimit("NAMESPACE");
@@ -164,6 +159,7 @@ function getAllEnv(userData) {
164
159
  host: host,
165
160
  clientID: clientID,
166
161
  appName: appName,
162
+ pkce: pkce,
167
163
 
168
164
  keepAlive: null,
169
165
  useToken: process.env.USETOKEN,
@@ -230,7 +226,7 @@ for (let key in process.env) {
230
226
  }
231
227
  }
232
228
  }
233
-
229
+ userData.APPNAME = l.appName;
234
230
  env = {
235
231
  LOGONPAYLOAD: l,
236
232
  APPENV: userData,
@@ -65,11 +65,15 @@ async function iSASauth (server, options) {
65
65
  provider : provider,
66
66
  password : uuid.v4(),
67
67
  clientId : options.clientId,
68
- clientSecret: options.clientSecret,
68
+ clientSecret: (options.clientSecret == null) ? '' : options.clientSecret,
69
69
  // isSameSite : options.isSameSite,
70
70
  isSecure : options.isSecure
71
71
  };
72
- // console.log('SASAuth options', bellAuthOptions);
72
+
73
+ if (options.pkce === true) {
74
+ bellAuthOptions.pkce = 'S256';
75
+ }
76
+
73
77
  debug('belloptions', bellAuthOptions);
74
78
  server.log('SASAuth',bellAuthOptions);
75
79
  await server.register(bell);
@@ -32,7 +32,6 @@ module.exports = async function appCookie (server, options){
32
32
  } else {
33
33
  sid = session.sid;
34
34
  }
35
- console.log('appcookie sid', sid);
36
35
  if (sid != null) {
37
36
  credentials = await req.server.app.cache.get(sid);
38
37
  }
@@ -16,6 +16,7 @@
16
16
  *
17
17
  */
18
18
 
19
+
19
20
  import {
20
21
  getApp,
21
22
  getApp2,
@@ -29,6 +30,7 @@ import {
29
30
  reactDev,
30
31
  proxyMapUri,
31
32
  } from "../handlers";
33
+ import setContext from './setContext';
32
34
  let debug = require("debug")("routes");
33
35
  module.exports = function setDefaultRoutes(server, options) {
34
36
  debug("setDefaultRoutes");
@@ -51,7 +53,7 @@ module.exports = function setDefaultRoutes(server, options) {
51
53
  }
52
54
  let getAppb = getApp.bind(
53
55
  null,
54
- process.env.USETOKEN === "YES" ? options : null
56
+ (process.env.USETOKEN != null && process.env.USETOKEN.toUpperCase() === "TRUE") ? options : null
55
57
  );
56
58
 
57
59
  console.log("Default strategy", authDefault);
@@ -249,7 +251,10 @@ module.exports = function setDefaultRoutes(server, options) {
249
251
  };
250
252
  debug(pr);
251
253
  defaultTable.push(pr);
252
-
254
+ // now set pre for all default routes
255
+ defaultTable.forEach((r) => {
256
+ r.options.pre = [{method: setContext, assign: 'context'}];
257
+ });
253
258
  let routeTables =
254
259
  uTable !== null ? defaultTable.concat(uTable) : defaultTable;
255
260
  server.route(routeTables);
@@ -25,22 +25,19 @@ function setupUserRoutes (u, options) {
25
25
  let ux = (typeof u === 'function') ? u() : u;
26
26
  let routes = ux.map(rx => {
27
27
  //let rx = {...r};
28
- /* change it to options */
29
- if (rx.config != null) {
30
- rx.options = {...rx.config};
31
- delete rx.config;
32
- }
28
+
33
29
  if (rx.options.pre == null) {
34
30
  rx.options.pre = [{method: setContext, assign: 'context'}];
35
31
  } else{
36
32
  rx.options.pre.push([{method: setContext, assign: 'context'}]);
37
33
  }
34
+ console.log(rx.options.pre);
38
35
  if (rx.options.auth === true) {
39
36
  rx.options.auth = options.authDefault;
40
37
  } else if (rx.options.auth === 'logon') {
41
38
  rx.options.auth = options.authLogon;
42
39
  }
43
-
40
+ console.log('route auth', rx.options.auth);
44
41
  return rx;
45
42
  });
46
43
  return routes;
package/.env.proxy DELETED
@@ -1,32 +0,0 @@
1
- APPHOST=localhost
2
- # APPENTRY=index.html
3
- APPENTRY=indexProxy.html
4
- APPLOC=./public
5
- VIYA_SERVER=
6
- APPENV_PROXYSERVER=https://localhost:8080/appBuilder/proxy
7
-
8
- APPPORT=8080
9
- APPNAME=appBuilder
10
- AUTHFLOW=server
11
- CLIENTID=appbuilder
12
- CLIENTSECRET=jellico
13
- REDIRECT=
14
-
15
- # APPDIR=./appDir
16
- # HTTPS=true
17
- USELOGON=YES
18
-
19
- USETOKEN=YES
20
- # SHOWENV=YES
21
- # APPENV=
22
- APPENV_XYZ=AA
23
- APPENV_BAD=
24
-
25
- # APPENV_PUPID=NSHOST=https://sas-logon-app.viya.svc.cluster.local
26
-
27
- # NAMESPACE=viya
28
- # TLS_CERT=./certs/tls.crt
29
- # TLS_KEY=./certs/tls.key
30
-
31
-
32
-
package/tls/viyatls.sh DELETED
@@ -1,3 +0,0 @@
1
- kubectl cp $(kubectl get pod | grep "sas-consul-server-0" | awk -F" " '{print $1}'):security/ca.crt ./ca.crt
2
- kubectl cp $(kubectl get pod | grep "sas-consul-server-0" | awk -F" " '{print $1}'):security/tls.crt ./tls.crt
3
- kubectl cp $(kubectl get pod | grep "sas-consul-server-0" | awk -F" " '{print $1}'):security/tls.key ./tls.key