profoundjs 6.6.1 → 7.0.0
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/htdocs/iscroll/iscroll.js +3 -0
- package/htdocs/profoundui/proddata/images/FlatIcons/16x16/downloadSkin.png +0 -0
- package/htdocs/profoundui/proddata/images/FlatIcons/16x16/uploadSkin.png +0 -0
- package/htdocs/profoundui/proddata/js/designer.js +3557 -3513
- package/htdocs/profoundui/proddata/js/runtime.js +1381 -1377
- package/index.js +2 -4
- package/package.json +4 -5
- package/profound.jse +1 -1
- package/setup/completeInstall.js +31 -16
- package/setup/gen_key.js +6 -0
- package/setup/get_pjscall_key.js +6 -0
- package/setup/install_info.json +7 -0
- package/setup/modules/oauth2sample/.noderun/ide_settings.json +9 -0
- package/setup/modules/oauth2sample/.noderun/settings.json +14 -0
- package/setup/modules/oauth2sample/README.md +251 -0
- package/setup/modules/oauth2sample/authpage.js +178 -0
- package/setup/modules/oauth2sample/mywebservices.api.json +47 -0
- package/setup/modules/oauth2sample/oautils.js +257 -0
- package/setup/modules/oauth2sample/settings.js +79 -0
- package/setup/modules/oauth2sample/useful-app.json +470 -0
- package/setup/pjsdist.savf +0 -0
- package/setup/setup.js +58 -31
- package/setup/encrypt_client_file.js +0 -4
package/setup/completeInstall.js
CHANGED
|
@@ -47,7 +47,8 @@ const IBMi = iutils.isIBMi();
|
|
|
47
47
|
"h",
|
|
48
48
|
"c",
|
|
49
49
|
"s",
|
|
50
|
-
"v"
|
|
50
|
+
"v",
|
|
51
|
+
"installSamples"
|
|
51
52
|
],
|
|
52
53
|
string: []
|
|
53
54
|
};
|
|
@@ -62,6 +63,7 @@ const IBMi = iutils.isIBMi();
|
|
|
62
63
|
-c, --configure Update config.js, if present.
|
|
63
64
|
-s, --silent No interactive prompts.
|
|
64
65
|
-v, --validate Validate silent mode arguments only.
|
|
66
|
+
--installSamples Install sample code
|
|
65
67
|
|
|
66
68
|
Creates config.js, if not present, and completes installation.
|
|
67
69
|
--configure can be specified to update an existing config.js before proceeding.
|
|
@@ -73,6 +75,9 @@ If --silent is specified, configuration values are read from arguments instead.
|
|
|
73
75
|
If arguments are not valid, the script will exit with code 2 and error messages
|
|
74
76
|
will be output to stderr in JSON format.
|
|
75
77
|
|
|
78
|
+
--installSamples installs sample modules, workspaces, plugins, and other sample
|
|
79
|
+
code.
|
|
80
|
+
|
|
76
81
|
Valid arguments for --silent mode:
|
|
77
82
|
|
|
78
83
|
` + await buildArgHelp();
|
|
@@ -146,10 +151,12 @@ Valid arguments for --silent mode:
|
|
|
146
151
|
const gitSupported = ["win32", "darwin", "linux"].includes(os.platform());
|
|
147
152
|
|
|
148
153
|
const configPath = iutils.getConfigPath();
|
|
154
|
+
// Note: because an eval() may set the "config" variable subsequently, "config" cannot be a const.
|
|
155
|
+
// eslint-disable-next-line prefer-const
|
|
149
156
|
let config = fileExists(configPath) ? require(configPath) : {};
|
|
150
157
|
const strtcpsvr_config = {};
|
|
151
158
|
|
|
152
|
-
let svrname, autostart, ccsid, nodePath;
|
|
159
|
+
let svrname, autostart, ccsid, nodePath, installSamples;
|
|
153
160
|
|
|
154
161
|
// If config.js doesn't exist, or if --configure is passed, prompt and create/update config.js.
|
|
155
162
|
if (!fileExists(configPath) || args["configure"] === true) {
|
|
@@ -256,6 +263,7 @@ Valid arguments for --silent mode:
|
|
|
256
263
|
delete config.profounduiLibrary;
|
|
257
264
|
}
|
|
258
265
|
}
|
|
266
|
+
|
|
259
267
|
fs.writeFileSync(configPath, stringifyConfig(config));
|
|
260
268
|
console.log("");
|
|
261
269
|
console.log("config.js updated.");
|
|
@@ -263,7 +271,7 @@ Valid arguments for --silent mode:
|
|
|
263
271
|
}
|
|
264
272
|
else {
|
|
265
273
|
let content = fs.readFileSync(path.join(iutils.getSetupDir(), "config.js"), "utf8");
|
|
266
|
-
eval("config = " + content.
|
|
274
|
+
eval("config = " + content.substring(content.indexOf("{")));
|
|
267
275
|
config.port = answers.port;
|
|
268
276
|
if (answers.connectorLibrary) {
|
|
269
277
|
config.connectorLibrary = answers.connectorLibrary;
|
|
@@ -304,6 +312,8 @@ Valid arguments for --silent mode:
|
|
|
304
312
|
console.log("config.js created.");
|
|
305
313
|
console.log("");
|
|
306
314
|
}
|
|
315
|
+
|
|
316
|
+
installSamples = answers.installSamples == true;
|
|
307
317
|
}
|
|
308
318
|
|
|
309
319
|
// Complete installation.
|
|
@@ -316,7 +326,7 @@ Valid arguments for --silent mode:
|
|
|
316
326
|
if (config.connectorLibrary !== undefined) {
|
|
317
327
|
setupArgs.push("--ibmi-connector-library");
|
|
318
328
|
}
|
|
319
|
-
if (svrname) {
|
|
329
|
+
if (svrname) {
|
|
320
330
|
setupArgs.push("--ibmi-instance=" + svrname);
|
|
321
331
|
if (autostart) {
|
|
322
332
|
setupArgs.push("--ibmi-instance-autostart");
|
|
@@ -335,6 +345,11 @@ Valid arguments for --silent mode:
|
|
|
335
345
|
if (gitSupported && config.gitSupport !== false && nodeMajorVersion < 18) {
|
|
336
346
|
setupArgs.push("--nodegit");
|
|
337
347
|
}
|
|
348
|
+
|
|
349
|
+
if (installSamples || args.installSamples) {
|
|
350
|
+
setupArgs.push("--installSamples");
|
|
351
|
+
}
|
|
352
|
+
|
|
338
353
|
if (setupArgs.length === 0) {
|
|
339
354
|
setupArgs = "";
|
|
340
355
|
}
|
|
@@ -372,7 +387,7 @@ async function ask(questionParm, configs, recursive) {
|
|
|
372
387
|
if (typeof defaultAnswer !== "string") defaultAnswer = String(defaultAnswer);
|
|
373
388
|
|
|
374
389
|
if (defaultAnswer !== "") {
|
|
375
|
-
|
|
390
|
+
const lastChar = question.substr(question.length - 1, 1);
|
|
376
391
|
if (lastChar === ":" || lastChar === "?") {
|
|
377
392
|
question = question.substr(0, question.length - 1) + " (" + defaultAnswer + ")" + lastChar;
|
|
378
393
|
}
|
|
@@ -408,9 +423,9 @@ async function ask(questionParm, configs, recursive) {
|
|
|
408
423
|
}
|
|
409
424
|
|
|
410
425
|
function fileExists(file) {
|
|
411
|
-
|
|
426
|
+
let exists = false;
|
|
412
427
|
try {
|
|
413
|
-
|
|
428
|
+
const stat = fs.statSync(file);
|
|
414
429
|
if (stat && stat.isFile()) exists = true;
|
|
415
430
|
}
|
|
416
431
|
catch (err) {
|
|
@@ -430,7 +445,7 @@ function validateName(name) {
|
|
|
430
445
|
function validateLibrary(name) {
|
|
431
446
|
const err = validateName(name);
|
|
432
447
|
if (err === true) {
|
|
433
|
-
|
|
448
|
+
if (iutils.libraryExists(name)) return "Library " + name.toUpperCase() + " already exists. Please use a different name.";
|
|
434
449
|
}
|
|
435
450
|
else return err;
|
|
436
451
|
return true;
|
|
@@ -439,8 +454,8 @@ function validateLibrary(name) {
|
|
|
439
454
|
function validateServer(name) {
|
|
440
455
|
const err = validateName(name);
|
|
441
456
|
if (err === true) {
|
|
442
|
-
|
|
443
|
-
|
|
457
|
+
const exists = iutils.getIBMiInstances().some(el => el.name === name.toUpperCase());
|
|
458
|
+
if (exists) return "Server Instance " + name.toUpperCase() + " already exists. Please use a different name.";
|
|
444
459
|
}
|
|
445
460
|
else return err;
|
|
446
461
|
return true;
|
|
@@ -487,12 +502,12 @@ function stringifyConfig(config) {
|
|
|
487
502
|
// JavaScript functions (like connectorIPFilter) will be lost when doing JSON.stringify,
|
|
488
503
|
// so we need to do some magic to preserve them.
|
|
489
504
|
|
|
490
|
-
|
|
505
|
+
const funkz = [];
|
|
491
506
|
let configString = "\nmodule.exports = ";
|
|
492
507
|
|
|
493
508
|
configString += JSON.stringify(config, (key, val) => {
|
|
494
509
|
if (typeof val === "function") {
|
|
495
|
-
|
|
510
|
+
const n = funkz.length;
|
|
496
511
|
funkz.push(val.toString());
|
|
497
512
|
return `___function${n}___`;
|
|
498
513
|
}
|
|
@@ -501,7 +516,7 @@ function stringifyConfig(config) {
|
|
|
501
516
|
|
|
502
517
|
configString += "\n";
|
|
503
518
|
|
|
504
|
-
for (
|
|
519
|
+
for (let i = 0; i < funkz.length; i++) {
|
|
505
520
|
configString = configString.replace(`"___function${i}___"`, funkz[i]);
|
|
506
521
|
}
|
|
507
522
|
|
|
@@ -605,7 +620,7 @@ function getPUIInstanceData(name) {
|
|
|
605
620
|
}
|
|
606
621
|
|
|
607
622
|
// Replace PUI Developer name in doc root, if set.
|
|
608
|
-
|
|
623
|
+
const developer = getPUIDefine("DEVELOPER");
|
|
609
624
|
if (developer) {
|
|
610
625
|
docRoot = docRoot.replace(/\$\{DEVELOPER\}/g, developer);
|
|
611
626
|
}
|
|
@@ -824,13 +839,13 @@ function validate(question, answer) {
|
|
|
824
839
|
validator = validatePUIInstance;
|
|
825
840
|
}
|
|
826
841
|
else if (question.id === "connectorLibrary") {
|
|
827
|
-
|
|
842
|
+
validator = validateLibrary;
|
|
828
843
|
}
|
|
829
844
|
else if (question.id === "connectorIASP") {
|
|
830
845
|
validator = validateIASP;
|
|
831
846
|
}
|
|
832
847
|
else if (question.id === "strtcpsvr_svrname") {
|
|
833
|
-
|
|
848
|
+
validator = validateServer;
|
|
834
849
|
}
|
|
835
850
|
else if (question.type === "boolean") {
|
|
836
851
|
validator = validateYesNo;
|
package/setup/gen_key.js
ADDED
package/setup/install_info.json
CHANGED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
# OAuth2 Sample
|
|
2
|
+
This sample workspace provides a sample sign on screen, a screen for calling an API, a sample API, and back-end code to glue these parts together.
|
|
3
|
+
|
|
4
|
+
The sample application, "useful-app.json", requires users to authenticate via an OAuth2 provider. Once authenticated, a user can click a button causing the Profound.js (PJS) server to call the provided sample API route, defined in "mywebservices.api.json". The HTTP request to the API route includes an HTTP header containing the user's OAuth2 token, which verifies the user's identity. The API will return a message if the PJS security store permits the user to access the sample API.
|
|
5
|
+
|
|
6
|
+
Disclaimer: this sample is provided to demonstrate OAuth2 for Profound API. This example and its code are not meant as a demonstration of good sign-on coding and are provided AS-IS with no warranty. The sign-on code should not be used in production servers.
|
|
7
|
+
## Setup
|
|
8
|
+
|
|
9
|
+
### 1. Install Sample Workspace: oauth2sample
|
|
10
|
+
If you are reading this README from within your own Profound.js instance inside the /modules/oauth2sample/ directory, then you can skip this step.
|
|
11
|
+
|
|
12
|
+
Otherwise, the oauth2sample workspace can be installed by running this command from the directory of your Profound.js installation:
|
|
13
|
+
`node complete_install --installSamples`
|
|
14
|
+
|
|
15
|
+
If you do not already have a config.js file, then you will be prompted for some settings. When asked about sample code, answer y for yes.
|
|
16
|
+
|
|
17
|
+
For example, your terminal screen may look like below:
|
|
18
|
+
```
|
|
19
|
+
$ cd /your-profoundjs-instance/
|
|
20
|
+
$ node complete_install --installSamples
|
|
21
|
+
|
|
22
|
+
Specify port number for Profound.js server (8081):
|
|
23
|
+
Install with Git integration (y)? n
|
|
24
|
+
Install sample code (y)? y
|
|
25
|
+
|
|
26
|
+
config.js updated.
|
|
27
|
+
[...]
|
|
28
|
+
Copying pjssamples.
|
|
29
|
+
Copying sample workspace into /your-profoundjs-instance/modules/oauth2sample
|
|
30
|
+
[...]
|
|
31
|
+
Profound.js installation complete.
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### 2. Setup an app with an OAuth2 Provider
|
|
35
|
+
Before the oauth2sample app can authenticate users via OAuth2, an app must be defined with an OAuth2 authorization server (identity provider).
|
|
36
|
+
|
|
37
|
+
An authorization server authorizes a user or an API consumer to access some resource. Common providers of authorization servers are Google, Facebook, and Microsoft.
|
|
38
|
+
|
|
39
|
+
Basic steps with the provider should include:
|
|
40
|
+
* creating an account if you do not already have one.
|
|
41
|
+
* specifying one or more Redirect URIs.
|
|
42
|
+
* obtaining a Client ID
|
|
43
|
+
* obtaining a Client Secret
|
|
44
|
+
|
|
45
|
+
You may use Google, Amazon, Microsoft, Facebook, GitHub, Twitter, or any other authorization server. This guide covers implementing with Microsoft, Google, and GitHub. The sample app requires at least one OAuth2 provider to be configured.
|
|
46
|
+
|
|
47
|
+
#### Microsoft Entra (Azure Active Directory)
|
|
48
|
+
1. Log into your Microsoft Entra / Azure account and go to: https://entra.microsoft.com/#blade/Microsoft_AAD_IAM/TenantOverview.ReactView
|
|
49
|
+
(or https://entra.microsoft.com/#home).
|
|
50
|
+
2. In the left panel, click Applications > App registrations.
|
|
51
|
+
1. Click the "+ New registration" near the top of the screen.
|
|
52
|
+
2. Enter a name, e.g. "OAuth2 Sample"
|
|
53
|
+
3. For Supported account types, specify the intended users; e.g. "Accounts in this organizational directory only ... Single tenant".
|
|
54
|
+
4. For Redirect URI specify:
|
|
55
|
+
* Platform: "Platform: Single-page application (SPA)"
|
|
56
|
+
* URI: specify a URI used to reach the sample express route running on your PJS server and a special parameter for redirecting; e.g., https://localhost:8081/run/oauth2sample/authpage?redir=1
|
|
57
|
+
5. Click "Register"
|
|
58
|
+
3. After clicking Register you should be shown an "App registration" screen for the new app.
|
|
59
|
+
1. Copy the "Application (client) ID" value, and paste it into a temporary text file. That value will become the "client_id" in the settings.json for the workspace.
|
|
60
|
+
2. You should see an "Endpoints" button near the top of the screen. Click it to see some required URLs.
|
|
61
|
+
* Copy the "OAuth 2.0 authorization endpoint (v2)" URL into a temporary text file. (This will become the "authorizationUrl" in the openapi.json)
|
|
62
|
+
* Copy the "OAuth 2.0 token endpoint (v2)" URL into a temporary text file. (This will become the "tokenUrl" in the openapi.json)
|
|
63
|
+
4. Click the "Authentication" link on the left panel under Manage.
|
|
64
|
+
1. Make sure you see a platform, "Single-page application" with "Redirect URIs" and an entry with the redirect URI that you set previously. If you do not, then set one or more URIs now.
|
|
65
|
+
2. Do NOT check anything under "Implicit grant and hybrid flows"
|
|
66
|
+
3. Under "Supported account types" you should see "Accounts in this organizational directory only" or what you picked previously.
|
|
67
|
+
4. Leave "Allow public client flows" > "Enable the following mobile and desktop flows": as "No".
|
|
68
|
+
5. Click the "Certificates & secrets" link on the left panel under Manage.
|
|
69
|
+
1. Click the "Client secrets" section.
|
|
70
|
+
2. Click on the "+ New client secret" link.
|
|
71
|
+
3. Set a name, e.g., "OAuth2 smaple secret"
|
|
72
|
+
4. Set an expiration to something, e.g. "90 days (3 months)"
|
|
73
|
+
5. Click "Add"
|
|
74
|
+
6. While it is visible, copy the "Value" and paste it into a temporary text file. The value will become the "client_secret" later in your settings.js file in the workspace.
|
|
75
|
+
6. Click the "API permissions" link on the left panel under Manage.
|
|
76
|
+
1. Under "Configured permissions" click "+ Add a permission".
|
|
77
|
+
* Specify "Microsft Graph"
|
|
78
|
+
* Choose "Delegated permissions
|
|
79
|
+
* Add and enable permissions for "User.read" (Sign in and read user profile), "email" (View user's email address), and "openid" (Sign users in).
|
|
80
|
+
7. Paste the client_id and client_secret into the /modules/oauth2sample/settings.js file under the providerSettings.Microsoft entries, replacing the text "__enter-your-client-id-here__" and "__enter-your-client-secret-here__".
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
#### Google
|
|
84
|
+
1. Log in to a Google account and go to https://console.cloud.google.com/apis/credentials
|
|
85
|
+
2. Click "Create Project " if there is none. Input the following data and click "Create".
|
|
86
|
+
1. Project Name: PAPI OAuth2 Project (e.g.)
|
|
87
|
+
2. Location: (pick the default)
|
|
88
|
+
3. On the Left Panel, click "OAuth consent screen".
|
|
89
|
+
1. For User Type, choose "External " and click "Create".
|
|
90
|
+
2. For App Information, input the following data and click "Save and Continue".
|
|
91
|
+
* App name: PAPI OAuth2 App (e.g.)
|
|
92
|
+
* User support email: (pick the default)
|
|
93
|
+
* Developer contact information email addresses: (same as support)
|
|
94
|
+
3. For Scopes and Test Users, click "Save and Continue" to skip.
|
|
95
|
+
4. For Summary, click "Back to Dashboard".
|
|
96
|
+
4. On the Left Panel, click "Credentials". Go to "+ Create Credentials -> OAuth client ID". Input the following data and click "Create".
|
|
97
|
+
* Application type: Web application
|
|
98
|
+
* Name: PAPI OAuth2 Client 1 (e.g.)
|
|
99
|
+
* Authorized redirect URIs: The URL of your Profound.js instance including the special path to the custom Express program, authpage: /run/oauth2sample/authpage?redir=1 ; Examples:
|
|
100
|
+
* https://myapp.example.com/run/oauth2sample/authpage?redir=1
|
|
101
|
+
* http://localhost:8081/run/oauth2sample/authpage?redir=1
|
|
102
|
+
5. Copy the Client ID and the Client Secret and store it in the /modules/oauth2sample/settings.js file under Google.
|
|
103
|
+
|
|
104
|
+
#### GitHub
|
|
105
|
+
1. Log in at https://github.com/
|
|
106
|
+
2. Go to "Settings -> Developer Settings -> OAuth Apps -> New OAuth App".
|
|
107
|
+
3. Input the following and click "Register Application".
|
|
108
|
+
* Application Name: PAPI OAuth2 Application (e.g.)
|
|
109
|
+
* Homepage URL: Specify the URL you use to reach your Profound.js instance in your browser; e.g., http://localhost:8081/
|
|
110
|
+
* Authorization callback URL: The URL of your Profound.js instance including the special path, /run/oauth2sample/authpage?redir=1 ; e.g. http://localhost:8081/run/oauth2sample/authpage?redir=1
|
|
111
|
+
4. Click "Generate a new client secret" and authenticate on the security prompt.
|
|
112
|
+
5. Copy the Client ID and the Client Secret and store it in the /modules/oauth2sample/settings.js file under GitHub.
|
|
113
|
+
|
|
114
|
+
### 3. Configure Profound.js for OpenAPI
|
|
115
|
+
Your Profound.js instance will provide resources for users or API consumers and will handle the authentication. Your Profound.js server should already be setup and running. (The Profound.js server is considered the Resource Server in OAuth2 terminology. https://www.oauth.com/oauth2-servers/the-resource-server/)
|
|
116
|
+
|
|
117
|
+
Configuration for the OAuth2 component of PAPI is in the openapi.json file, which will exist in the base directory of your Profound.js installation when an OpenAPI security store is configured. (To setup the security store, please see https://docs.profoundlogic.com/Profound+API/Authentication+-+Authorization/Security+Store+Configuration)
|
|
118
|
+
|
|
119
|
+
If the openapi.json file does not exist yet, then the file can be created by navigating to the IDE (e.g. http://localhost:8081/ide), then clicking the "API Options" toolbar button, "OpenAPI Configuration". You will then be shown a basic editor with sample JSON for the openapi.json and can save the file to create it.
|
|
120
|
+
|
|
121
|
+
If the openapi.json file already exists but does not have entries for OAuth2 under components.securitySchemes, then those entries need to be added.
|
|
122
|
+
|
|
123
|
+
To make sure you test your security settings correctly, ensure the value of "x-allowAnonymous" is false. The security property should be an array of objects, including which authorization schemes are permitted. e.g. just to allow OAuth2:
|
|
124
|
+
```
|
|
125
|
+
"security": [
|
|
126
|
+
{
|
|
127
|
+
"OAuth2": []
|
|
128
|
+
}
|
|
129
|
+
]
|
|
130
|
+
```
|
|
131
|
+
Or to allow either "ApiKeyAuth" or an OAuth2 security scheme defined as "OAuth2_Example":
|
|
132
|
+
```
|
|
133
|
+
"security": [
|
|
134
|
+
{
|
|
135
|
+
"OAuth2_Example": []
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
"ApiKeyAuth": []
|
|
139
|
+
}
|
|
140
|
+
]
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Instructions for adding entries for OAuth2 into openapi.json can be found here: https://docs.profoundlogic.com//Profound+API/Authentication+-+Authorization/OpenAPI+Configuration
|
|
144
|
+
|
|
145
|
+
#### 3.1 Sample openapi.json
|
|
146
|
+
```
|
|
147
|
+
{
|
|
148
|
+
"openapi": "3.0.3",
|
|
149
|
+
"info": {
|
|
150
|
+
"title": "APIs",
|
|
151
|
+
"version": "1.0.0"
|
|
152
|
+
},
|
|
153
|
+
"components": {
|
|
154
|
+
"securitySchemes": {
|
|
155
|
+
"ApiKeyAuth": {
|
|
156
|
+
"type": "apiKey",
|
|
157
|
+
"in": "header",
|
|
158
|
+
"name": "X-API-Key"
|
|
159
|
+
},
|
|
160
|
+
"OAuth2_MS_Entra_Identity": {
|
|
161
|
+
"type": "oauth2",
|
|
162
|
+
"description": "Microsoft Azure Active Directory (Entra)",
|
|
163
|
+
"flows": {
|
|
164
|
+
"authorizationCode": {
|
|
165
|
+
"authorizationUrl": "https://login.microsoftonline.com/__your-tenant-id__/oauth2/v2.0/authorize",
|
|
166
|
+
"tokenUrl": "https://login.microsoftonline.com/__your-tenant-id__/oauth2/v2.0/token",
|
|
167
|
+
"refreshUrl": "https://example.com",
|
|
168
|
+
"scopes": {
|
|
169
|
+
"openid": "required for consent page",
|
|
170
|
+
"User.Read": "read user info, userPrincipalName"
|
|
171
|
+
},
|
|
172
|
+
"x-userinfoUrl": "https://graph.microsoft.com/v1.0/me",
|
|
173
|
+
"x-userinfoField": "userPrincipalName"
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
"OAuth2_GitHub": {
|
|
178
|
+
"type": "oauth2",
|
|
179
|
+
"flows": {
|
|
180
|
+
"authorizationCode": {
|
|
181
|
+
"authorizationUrl": "https://github.com/login/oauth/authorize",
|
|
182
|
+
"tokenUrl": "https://github.com/login/oauth/access_token",
|
|
183
|
+
"refreshUrl": "not implemented for GitHub",
|
|
184
|
+
"scopes": {
|
|
185
|
+
"user": "GitHub user profile"
|
|
186
|
+
},
|
|
187
|
+
"x-userinfoUrl": "https://api.github.com/user",
|
|
188
|
+
"x-userinfoField": "login",
|
|
189
|
+
"x-cacheTokenTTL": 10
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
"OAuth2_Google": {
|
|
194
|
+
"type": "oauth2",
|
|
195
|
+
"flows": {
|
|
196
|
+
"authorizationCode": {
|
|
197
|
+
"authorizationUrl": "https://accounts.google.com/o/oauth2/auth",
|
|
198
|
+
"tokenUrl": "https://oauth2.googleapis.com/token",
|
|
199
|
+
"refreshUrl": "https://oauth2.googleapis.com/token",
|
|
200
|
+
"scopes": {
|
|
201
|
+
"email": "Google email address"
|
|
202
|
+
},
|
|
203
|
+
"x-userinfoUrl": "https://www.googleapis.com/oauth2/v3/userinfo",
|
|
204
|
+
"x-userinfoField": "email",
|
|
205
|
+
"x-cacheTokenTTL": 5
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
},
|
|
211
|
+
"x-allowAnonymous": false,
|
|
212
|
+
"security": [
|
|
213
|
+
{
|
|
214
|
+
"OAuth2_MS_Entra_Identity": []
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
"OAuth2_GitHub": []
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
"OAuth2_Google": []
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
"ApiKeyAuth": []
|
|
224
|
+
}
|
|
225
|
+
]
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### 4. Specify Provider Details in Settings
|
|
230
|
+
Modify the oauth2sample/settings.js file.
|
|
231
|
+
|
|
232
|
+
Inside this workspace is a file, settings.js, which contains some values that should be edited. Read the comments in the file to understand what each setting does.
|
|
233
|
+
|
|
234
|
+
At a minimum, this sample app requires a provider to be set, and one entry must be setup in "providerSettings", where the entry's property name matches the value of the "provider" property. e.g., if your "provider" is "Microsoft", then you should have an object under the property name, "Microsoft", inside the "propertySettings" object.
|
|
235
|
+
|
|
236
|
+
The values inside of your "propertySettings" entry will include at least a client_id and client_secret. Depending on your OAuth2 provider, more settings may be required. If your provider is not listed, then read the options under the `[example]` to determine what your provider needs.
|
|
237
|
+
|
|
238
|
+
Set the serverDomainName to the domain name that you will use to access your PJS server, or leave it as "localhost" if you are running the example locally.
|
|
239
|
+
|
|
240
|
+
### 5. Setup API Users
|
|
241
|
+
|
|
242
|
+
1. From your Profound.js instance's IDE:
|
|
243
|
+
2. Go to "Home Ribbon -> API Options -> User Authentication -> API Users -> Add New User "
|
|
244
|
+
3. Input the user or login name of an end-user authorized to use PAPI OAuth2 and choose "New -> oauth2 -> OK"
|
|
245
|
+
* Note: the username added under this dialog must match the user's account via the OAuth2 provider, e.g. email address or username.
|
|
246
|
+
|
|
247
|
+
## Run the App
|
|
248
|
+
Run the sample app from the starting point; e.g. http://localhost:8081/run/oauth2sample/authpage
|
|
249
|
+
|
|
250
|
+
## More information
|
|
251
|
+
For documentation on OAuth2 in Profound.js, see: https://docs.profoundlogic.com/PAPI+OAuth2
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Handle the initial authentication page and the redirect from the OAuth2 provider
|
|
3
|
+
* to the Rich Display File application, "useful-app".
|
|
4
|
+
*/
|
|
5
|
+
const crypto = require("crypto");
|
|
6
|
+
|
|
7
|
+
const HTML_HEAD = `<!doctype html>
|
|
8
|
+
<html>
|
|
9
|
+
<head>
|
|
10
|
+
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
|
11
|
+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
12
|
+
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, target-densitydpi=device-dpi">
|
|
13
|
+
<link href="/profoundui/proddata/css/profoundui.css" rel="stylesheet" type="text/css">`;
|
|
14
|
+
|
|
15
|
+
const HTML_TAIL = "</body></html>";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Route a request to this URL to either show the initial auth page, or draw the redirect page.
|
|
19
|
+
* @param {HTTPRequest} request
|
|
20
|
+
* @param {HTTPResponse} response
|
|
21
|
+
*/
|
|
22
|
+
function authpage(request, response) {
|
|
23
|
+
response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
|
|
24
|
+
response.setHeader("Pragma", "no-cache");
|
|
25
|
+
response.setHeader("Expires", "0");
|
|
26
|
+
|
|
27
|
+
if (request.query.redir === "1") {
|
|
28
|
+
redirect(request, response);
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
start(request, response);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Draw the initial auth page.
|
|
37
|
+
*/
|
|
38
|
+
function start(request, response) {
|
|
39
|
+
// "state" is a cryptographically strong random sequence of characters, used later to
|
|
40
|
+
// protect against cross-site forgery attacks.
|
|
41
|
+
const formdata = {
|
|
42
|
+
response_type: "code",
|
|
43
|
+
redirect_uri: "",
|
|
44
|
+
client_id: "",
|
|
45
|
+
scope: "",
|
|
46
|
+
state: crypto.randomUUID(),
|
|
47
|
+
method: "GET",
|
|
48
|
+
url: "",
|
|
49
|
+
provider: ""
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
let message = request.query.error;
|
|
53
|
+
|
|
54
|
+
// Get 64 random characters needed for PKCE code verification. The characters must be URL-safe
|
|
55
|
+
// so that they can be passed without alteration to verify the code later.
|
|
56
|
+
let code_verifier_str = "";
|
|
57
|
+
while (code_verifier_str.length < 64) {
|
|
58
|
+
const rand = crypto.randomInt(45, 127);
|
|
59
|
+
// permitted: - . _ ~ 0-9 A-Z a-z
|
|
60
|
+
if (rand === 0x2d || rand === 0x2e || rand === 0x5f || rand === 0x7e ||
|
|
61
|
+
(rand >= 0x30 && rand <= 0x39) ||
|
|
62
|
+
(rand >= 0x41 && rand <= 0x5a) || (rand >= 0x61 && rand <= 0x7a)) {
|
|
63
|
+
code_verifier_str += String.fromCharCode(rand);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const hash = crypto.createHash("sha256");
|
|
68
|
+
hash.update(code_verifier_str, "utf8"); // read the random bytes into the hash object.
|
|
69
|
+
|
|
70
|
+
let oautils;
|
|
71
|
+
|
|
72
|
+
if (typeof message !== "string") message = "";
|
|
73
|
+
try {
|
|
74
|
+
oautils = require("./oautils.js");
|
|
75
|
+
|
|
76
|
+
formdata.redirect_uri = oautils.redirectURI;
|
|
77
|
+
formdata.client_id = oautils.client_id;
|
|
78
|
+
formdata.scope = oautils.scope;
|
|
79
|
+
|
|
80
|
+
formdata.code_challenge = hash.digest("base64url"); // the hash is expected to be base64url encoding.
|
|
81
|
+
formdata.code_challenge_method = "S256";
|
|
82
|
+
|
|
83
|
+
if (typeof oautils.authMethod === "string" && oautils.authMethod.length > 0 && oautils.authMethod !== "GET") {
|
|
84
|
+
formdata.method = oautils.authMethod;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
formdata.provider = oautils.provider;
|
|
88
|
+
formdata.url = oautils.authorizationUrl;
|
|
89
|
+
}
|
|
90
|
+
catch (err) {
|
|
91
|
+
message = String(err);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
response.send(`${HTML_HEAD}
|
|
95
|
+
<script type="text/javascript">
|
|
96
|
+
function init(){
|
|
97
|
+
/* Store value that are only available to pages in the same origin as this page. */
|
|
98
|
+
/* These values will persist even when the page redirects to the OAuth2 provider's site and back. */
|
|
99
|
+
localStorage.setItem("pjs_oadata", "${formdata.state}");
|
|
100
|
+
localStorage.setItem("pjs_codver", "${code_verifier_str}");
|
|
101
|
+
}
|
|
102
|
+
</script>
|
|
103
|
+
<style type="text/css">
|
|
104
|
+
pre {
|
|
105
|
+
color: #999999;
|
|
106
|
+
}
|
|
107
|
+
div#pui {
|
|
108
|
+
width: calc(100% - 20px);
|
|
109
|
+
left: 10px;
|
|
110
|
+
top: 10px;
|
|
111
|
+
}
|
|
112
|
+
#submit {
|
|
113
|
+
width: 100px;
|
|
114
|
+
height: 25px;
|
|
115
|
+
}
|
|
116
|
+
</style>
|
|
117
|
+
</head>
|
|
118
|
+
<body onload="init();">
|
|
119
|
+
<div id="pui">
|
|
120
|
+
<p>Click Sign On to use your ${formdata.provider} account to access this app.</p>
|
|
121
|
+
<form action="${formdata.url}" method="${formdata.method}">
|
|
122
|
+
<input type="hidden" name="response_type" value="${formdata.response_type}" />
|
|
123
|
+
<input type="hidden" name="redirect_uri" value="${formdata.redirect_uri}" />
|
|
124
|
+
<input type="hidden" name="client_id" value="${formdata.client_id}" />
|
|
125
|
+
<input type="hidden" name="scope" value="${formdata.scope}" />
|
|
126
|
+
<input type="hidden" name="state" value="${formdata.state}" />
|
|
127
|
+
<input type="hidden" name="code_challenge" value="${formdata.code_challenge}" />
|
|
128
|
+
<input type="hidden" name="code_challenge_method" value="${formdata.code_challenge_method}" />
|
|
129
|
+
|
|
130
|
+
<input type="submit" value="Sign On" class="pui-solid-button-yes blueprint-defaults" id="submit" />
|
|
131
|
+
</form>
|
|
132
|
+
<pre>${message}</pre>
|
|
133
|
+
</div>
|
|
134
|
+
${HTML_TAIL}`);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Respond to a redirect from the OAuth2 authentication server.
|
|
139
|
+
*/
|
|
140
|
+
async function redirect(request, response) {
|
|
141
|
+
let strResp;
|
|
142
|
+
try {
|
|
143
|
+
const rquery = request.query;
|
|
144
|
+
if (rquery.error) {
|
|
145
|
+
// Pass on any errors to the user.
|
|
146
|
+
throw new Error(`${rquery.error}\nDescription: ${rquery.error_description}\n${rquery.error_uri}\nState: ${rquery.state}`);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (typeof rquery.code !== "string" || rquery.code.length < 1 ||
|
|
150
|
+
typeof rquery.state !== "string" || rquery.state.length < 1) {
|
|
151
|
+
throw new Error("Authentication server did not provide code or state.");
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
strResp = `${HTML_HEAD}
|
|
155
|
+
<script type="text/javascript">
|
|
156
|
+
function sendTokens(){
|
|
157
|
+
const state = localStorage.getItem("pjs_oadata");
|
|
158
|
+
if (typeof state === "string" && state.length > 0 && state === "${rquery.state}"){
|
|
159
|
+
localStorage.setItem("pjs_oadata", "${rquery.code}");
|
|
160
|
+
window.location = "/run/oauth2sample/useful-app";
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
document.body.innerHTML = '<b>Error:</b> Unable to authenticate. Invalid state.';
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
</script>
|
|
167
|
+
</head>
|
|
168
|
+
<body onload="sendTokens();">Redirecting to session...</body>
|
|
169
|
+
</html>`;
|
|
170
|
+
}
|
|
171
|
+
catch (err) {
|
|
172
|
+
strResp = `${HTML_HEAD}</head><body><pre>${err.message}\n${err.stack}</pre>${HTML_TAIL}`;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
response.send(strResp);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
exports.run = authpage;
|