profoundjs 6.6.0 → 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/js/designer.js +3586 -3541
- 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
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"apiFileID": "c56222bf-43e7-4762-8722-e8b715126db4",
|
|
3
|
+
"routes": [
|
|
4
|
+
{
|
|
5
|
+
"apiRouteID": "21db819c-b91e-4540-8604-99d2e19bace9",
|
|
6
|
+
"name": "postTestService",
|
|
7
|
+
"enableCors": true,
|
|
8
|
+
"summary": "POST test service",
|
|
9
|
+
"method": "post",
|
|
10
|
+
"path": "/postTestService",
|
|
11
|
+
"inputs": [],
|
|
12
|
+
"outputs": [
|
|
13
|
+
{
|
|
14
|
+
"type": "string",
|
|
15
|
+
"name": "message"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"type": "string",
|
|
19
|
+
"name": "method"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"type": "string",
|
|
23
|
+
"name": "date_utc"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"type": "decimal",
|
|
27
|
+
"name": "random"
|
|
28
|
+
}
|
|
29
|
+
],
|
|
30
|
+
"subcategory": "",
|
|
31
|
+
"steps": [
|
|
32
|
+
{
|
|
33
|
+
"text": "Set API output",
|
|
34
|
+
"answers": {
|
|
35
|
+
"plugin": "Program Data:set-api-output",
|
|
36
|
+
"api-output-values": {
|
|
37
|
+
"message": "\"Congratulations! You made it to your PAPI endpoint!\"",
|
|
38
|
+
"method": "\"post\"",
|
|
39
|
+
"date_utc": "(new Date()).toJSON()",
|
|
40
|
+
"random": "Math.random()"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
]
|
|
45
|
+
}
|
|
46
|
+
]
|
|
47
|
+
}
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utilities to handle user authentication with an OAuth2 provider through a sample user interface.
|
|
3
|
+
* The code in useful-app.json or authpage.js depends on this module.
|
|
4
|
+
*/
|
|
5
|
+
module.exports = {};
|
|
6
|
+
const mod = module.exports;
|
|
7
|
+
|
|
8
|
+
const workspaceCfg = require("./settings.js");
|
|
9
|
+
|
|
10
|
+
mod.provider = workspaceCfg.provider;
|
|
11
|
+
if (typeof mod.provider !== "string" || mod.provider.length < 1) {
|
|
12
|
+
throw new Error("provider was not specified in oauth2sample config");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const provCfg = workspaceCfg.providerSettings[mod.provider];
|
|
16
|
+
if (typeof provCfg !== "object" || provCfg === null) {
|
|
17
|
+
throw new Error("oauth2sample config provider did not have matching providerSettings entry");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Gather the server scheme, HTTP or HTTPS, and secure or insecure port.
|
|
21
|
+
// WARNING: Always use encryption via HTTPS for production servers.
|
|
22
|
+
let scheme = "https";
|
|
23
|
+
let port = profound.settings.securePort;
|
|
24
|
+
if (port === 443) {
|
|
25
|
+
port = "";
|
|
26
|
+
}
|
|
27
|
+
else if (isNaN(port)) {
|
|
28
|
+
scheme = "http";
|
|
29
|
+
port = (profound.settings.port === 80 ? "" : ":" + profound.settings.port);
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
port = ":" + port;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// PROTOCOL_HOST_PORT becomes a URL for the OAuth2 redirect URL and other components in the sample app.
|
|
36
|
+
mod.PROTOCOL_HOST_PORT = `${scheme}://${workspaceCfg.serverDomainName}${port}`;
|
|
37
|
+
|
|
38
|
+
// This is the route in this sample application that handles the OAuth2 server's redirection of the
|
|
39
|
+
// client browser after authenticating or fetching an authentication code.
|
|
40
|
+
// Each application can have its own redirect URL.
|
|
41
|
+
mod.redirectURI = mod.PROTOCOL_HOST_PORT + "/run/oauth2sample/authpage?redir=1";
|
|
42
|
+
mod.startURI = mod.PROTOCOL_HOST_PORT + "/run/oauth2sample/authpage";
|
|
43
|
+
|
|
44
|
+
// Use provider-dependent settings if set, or use defaults.
|
|
45
|
+
mod.tokenUrlContentType = typeof provCfg.tokenUrlContentType === "string" ? provCfg.tokenUrlContentType : "";
|
|
46
|
+
mod.authMethod = typeof provCfg.authMethod === "string" ? provCfg.authMethod : "GET";
|
|
47
|
+
mod.tokenNoSecret = provCfg.tokenNoSecret === true;
|
|
48
|
+
|
|
49
|
+
// Headers needed by many APIs of popular OAuth2 providers.
|
|
50
|
+
const HTTP_REQ_HEADERS = {
|
|
51
|
+
"Accept": "application/json",
|
|
52
|
+
"Content-Type": "application/json"
|
|
53
|
+
};
|
|
54
|
+
if (typeof provCfg["User-Agent"] === "string") HTTP_REQ_HEADERS["User-Agent"] = provCfg["User-Agent"];
|
|
55
|
+
|
|
56
|
+
mod.client_id = provCfg.client_id;
|
|
57
|
+
mod.client_secret = provCfg.client_secret;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Exchange authorization code for OAuth tokens.
|
|
61
|
+
* Pre-condition: the RDF application has already validated the state.
|
|
62
|
+
* @param {String} code
|
|
63
|
+
* @param {undefined|String} codver code_verifier (for PKCE for some providers)
|
|
64
|
+
* @returns {Object} with properties: access_token, expires_in, refresh_token
|
|
65
|
+
*/
|
|
66
|
+
mod.fetchTokensAsync = async function(code, codver) {
|
|
67
|
+
// Assemble OAuth2 standard parameters.
|
|
68
|
+
const requestData = {
|
|
69
|
+
"grant_type": "authorization_code",
|
|
70
|
+
"redirect_uri": this.redirectURI,
|
|
71
|
+
"client_id": this.client_id,
|
|
72
|
+
"client_secret": this.client_secret,
|
|
73
|
+
code
|
|
74
|
+
};
|
|
75
|
+
if (typeof codver === "string") {
|
|
76
|
+
requestData.code_verifier = codver;
|
|
77
|
+
}
|
|
78
|
+
if (this.tokenNoSecret) {
|
|
79
|
+
// MS AD does not allow you to send client_secret. but GitHub requires it. Implementation dependent.
|
|
80
|
+
delete requestData.client_secret;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const headers = JSON.parse(JSON.stringify(HTTP_REQ_HEADERS)); // copy object.
|
|
84
|
+
headers.Origin = mod.PROTOCOL_HOST_PORT;
|
|
85
|
+
|
|
86
|
+
// Some providers use a different content-type header.
|
|
87
|
+
const tokenUrlContentType = this.tokenUrlContentType;
|
|
88
|
+
if (typeof tokenUrlContentType === "string" && tokenUrlContentType.length > 0) {
|
|
89
|
+
headers["Content-Type"] = tokenUrlContentType;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
let body;
|
|
93
|
+
const contentType = headers["Content-Type"];
|
|
94
|
+
if (typeof contentType === "string" && contentType.indexOf("application/x-www-form-urlencoded") === 0) {
|
|
95
|
+
body = "";
|
|
96
|
+
let sep = "";
|
|
97
|
+
for (const prop in requestData) {
|
|
98
|
+
body += `${sep}${prop}=${requestData[prop]}`;
|
|
99
|
+
sep = "&";
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
body = JSON.stringify(requestData);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
let responseData;
|
|
107
|
+
try {
|
|
108
|
+
responseData = await profound.httpRequest({
|
|
109
|
+
method: "POST",
|
|
110
|
+
uri: this.tokenUrl,
|
|
111
|
+
body,
|
|
112
|
+
json: true,
|
|
113
|
+
headers,
|
|
114
|
+
alwaysReadBody: true
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
catch (err) {
|
|
118
|
+
throw new Error(`Unable to fetch token from remote host: ${this.tokenUrl}`, { cause: err });
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (typeof responseData !== "object" || responseData === null) {
|
|
122
|
+
throw new Error("Request for OAuth2 token was missing expected object.");
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (typeof responseData.access_token !== "string") {
|
|
126
|
+
throw new Error("Request for OAuth2 token was missing expected access_token.", { cause: responseData });
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return responseData;
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Request an object with user identification by providing a user access token.
|
|
134
|
+
* @param {String} accessToken
|
|
135
|
+
* @returns {String}
|
|
136
|
+
* @throws
|
|
137
|
+
*/
|
|
138
|
+
mod.getUserIdAsync = async function(accessToken) {
|
|
139
|
+
const userinfoUrl = this["x-userinfoUrl"];
|
|
140
|
+
if (typeof userinfoUrl !== "string" || userinfoUrl.length < 1) {
|
|
141
|
+
throw new Error("No user info URL specified");
|
|
142
|
+
}
|
|
143
|
+
// Use access token to request user info from the resource server.
|
|
144
|
+
let userInfo;
|
|
145
|
+
try {
|
|
146
|
+
const headers = JSON.parse(JSON.stringify(HTTP_REQ_HEADERS)); // copy object.
|
|
147
|
+
headers.Authorization = "Bearer " + accessToken;
|
|
148
|
+
userInfo = await profound.httpRequest({
|
|
149
|
+
method: "GET",
|
|
150
|
+
uri: userinfoUrl,
|
|
151
|
+
json: true,
|
|
152
|
+
headers,
|
|
153
|
+
alwaysReadBody: true
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
catch (err) {
|
|
157
|
+
throw new Error(`Unable to get user info from remote host: ${userinfoUrl}`, { cause: err });
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (typeof userInfo !== "object" || userInfo === null) {
|
|
161
|
+
throw new Error("Response for user info request was invalid.");
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const userField = this["x-userinfoField"];
|
|
165
|
+
const userid = userInfo[userField];
|
|
166
|
+
if (typeof userid !== "string" || userid.length < 1) {
|
|
167
|
+
throw new Error("User info could not be read. Found: " + JSON.stringify(userInfo));
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return userid;
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
//
|
|
174
|
+
// Read OpenAPI OAuth2 properties from an openapi.json config file, and assign the properties to the module object.
|
|
175
|
+
//
|
|
176
|
+
|
|
177
|
+
// Get the User Info URL from the openapi.json file.
|
|
178
|
+
const path = require("path");
|
|
179
|
+
const filePath = path.join(profound.dir, (profound.DEV ? "profoundjs" : ""), "openapi.json");
|
|
180
|
+
|
|
181
|
+
const openAPIConfig = require(filePath).components.securitySchemes;
|
|
182
|
+
// Prevent the openapi.json module from being cached; changes should be permitted without restarting PJS.
|
|
183
|
+
let resolvedMod = require.resolve(filePath);
|
|
184
|
+
delete require.cache[resolvedMod];
|
|
185
|
+
|
|
186
|
+
// Find a security scheme in openapi.json for OAuth2. (This sample code assumes one has a description or key matching mod.provider.)
|
|
187
|
+
let oaSecurityScheme;
|
|
188
|
+
let lastOaSecurityScheme;
|
|
189
|
+
for (const secSchemeName in openAPIConfig) {
|
|
190
|
+
const secScheme = openAPIConfig[secSchemeName];
|
|
191
|
+
if (typeof secScheme === "object" && secScheme !== null && typeof secScheme.type === "string" && secScheme.type.length > 0) {
|
|
192
|
+
if (secScheme.type === "oauth2") {
|
|
193
|
+
lastOaSecurityScheme = secScheme;
|
|
194
|
+
|
|
195
|
+
let secSchemeDescr = secScheme.description;
|
|
196
|
+
if (typeof secSchemeDescr !== "string") secSchemeDescr = "";
|
|
197
|
+
else secSchemeDescr = secSchemeDescr.toLowerCase();
|
|
198
|
+
|
|
199
|
+
const lcProv = mod.provider.toLowerCase();
|
|
200
|
+
const secSchLC = secSchemeName.toLowerCase();
|
|
201
|
+
|
|
202
|
+
// Try to find the setting matching the provider, either in the "description" property or the key name.
|
|
203
|
+
if (secSchLC.indexOf(lcProv) >= 0 || secSchemeDescr.indexOf(lcProv) >= 0) {
|
|
204
|
+
// Use the first OAuth2 provider found in openapi.json that matches PROVIDER.
|
|
205
|
+
oaSecurityScheme = secScheme;
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const hint = " Check the openapi.json configuration file.";
|
|
213
|
+
|
|
214
|
+
if (typeof oaSecurityScheme !== "object" || oaSecurityScheme === null) {
|
|
215
|
+
if (typeof lastOaSecurityScheme === "object" && lastOaSecurityScheme !== null) {
|
|
216
|
+
// If the name of the provider could not match any description in openapi.json, then
|
|
217
|
+
// use the last valid OAuth2 entry.
|
|
218
|
+
oaSecurityScheme = lastOaSecurityScheme;
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
throw new Error(`Failed to find a security scheme of type oauth2.` + hint);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const flows = oaSecurityScheme.flows;
|
|
226
|
+
if (typeof flows !== "object" || flows === null) {
|
|
227
|
+
throw new Error("'flows' property is missing." + hint);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const authCode = oaSecurityScheme.flows.authorizationCode;
|
|
231
|
+
if (typeof authCode !== "object" || authCode === null) {
|
|
232
|
+
throw new Error("'authorizationCode' property is missing." + hint);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Scopes are required for identifying to the OAuth2 provider what resources are requested on behalf of the user.
|
|
236
|
+
// The requested scopes are the property names of the object defined in the "scopes" property in openapi.json.
|
|
237
|
+
// The "scope" data passed to APIs is expected to be a space-delimited list.
|
|
238
|
+
// See: https://swagger.io/specification/
|
|
239
|
+
const scopes = authCode.scopes;
|
|
240
|
+
mod.scope = typeof scopes === "object" && scopes !== null ? Object.keys(scopes).join(" ") : "";
|
|
241
|
+
|
|
242
|
+
// For convenience, copy these properties from the openapi.json into exported properties of this module.
|
|
243
|
+
// (PAPI security store user validation needs these properties defined in openapi.json.)
|
|
244
|
+
const copyList = ["authorizationUrl", "tokenUrl", "refreshUrl", "x-userinfoUrl", "x-userinfoField"];
|
|
245
|
+
copyList.forEach(el => {
|
|
246
|
+
const exportKey = el;
|
|
247
|
+
if (typeof authCode[el] === "string" || typeof authCode[el] === "boolean") {
|
|
248
|
+
mod[exportKey] = authCode[el];
|
|
249
|
+
}
|
|
250
|
+
else {
|
|
251
|
+
mod[exportKey] = "";
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
// Make sure oautils is not cached so that changes to config.js can be seen without restarting the PJS server.
|
|
256
|
+
resolvedMod = require.resolve(__filename);
|
|
257
|
+
delete require.cache[resolvedMod];
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Values in this sample configuration file should be changed to reflect the app you configured with your OAuth2 provider(s),
|
|
3
|
+
* and your Profound.js server configuration.
|
|
4
|
+
* This file gets read by oautils.js by the sample application code.
|
|
5
|
+
*/
|
|
6
|
+
module.exports = {
|
|
7
|
+
// If you will reach the sample app on your PJS server via some domain name, then enter it here; e.g.
|
|
8
|
+
// serverDomainName: "myapi.example.com",
|
|
9
|
+
serverDomainName: "localhost",
|
|
10
|
+
|
|
11
|
+
// Uncomment whichever provider you are using for the sample app, and comment out the others.
|
|
12
|
+
// The app uses whichever provider is set here; and, the sample UI used the text from this property.
|
|
13
|
+
provider: "[example]",
|
|
14
|
+
// provider: "Microsoft",
|
|
15
|
+
// provider: "GitHub",
|
|
16
|
+
// provider: "Google",
|
|
17
|
+
|
|
18
|
+
// Credentials for your OAuth2 app supplied by your provider when you configure an app; e.g. Microsoft, GitHub, Google, etc.
|
|
19
|
+
// WARNING: ensure that this file is protected from unauthorized access.
|
|
20
|
+
// Alternately, develop a different solution for storing and retrieving the OAuth2 app credentials.
|
|
21
|
+
|
|
22
|
+
// Provider-specific settings can be set here. Multiple providers can be coded, but the sample
|
|
23
|
+
// application currently only supports authenticating a user via one provider at a time.
|
|
24
|
+
providerSettings: {
|
|
25
|
+
"[example]": {
|
|
26
|
+
// Most authorization servers should require a client_id and client_secret.
|
|
27
|
+
client_id: "__enter-your-client-id-here__",
|
|
28
|
+
client_secret: "__enter-your-client-secret-here__",
|
|
29
|
+
|
|
30
|
+
// Authorization servers may require data to be sent via HTTP POST or by HTTP GET. The default "authMethod"
|
|
31
|
+
// in this sample is GET, so this property is only necessary when the method required is not GET:
|
|
32
|
+
authMethod: "GET",
|
|
33
|
+
|
|
34
|
+
// "User-Agent" is not set by default, but if an OAuth2 provider requires an HTTP Header for "User-Agent",
|
|
35
|
+
// then set this to some value.
|
|
36
|
+
"User-Agent": "Profound API",
|
|
37
|
+
|
|
38
|
+
// Some providers allow token data to be sent with the Header, "Content-Type: application/json", the default in
|
|
39
|
+
// this sample app. If the authorization server expects a different content-type, then set it here.
|
|
40
|
+
// The sample app sends the data as JSON by default. When content-type is x-www-form-urlencoded, then values are
|
|
41
|
+
// sent as &key=value pairs.
|
|
42
|
+
tokenUrlContentType: "application/x-www-form-urlencoded, charset=utf-8",
|
|
43
|
+
|
|
44
|
+
// When exchanging code for token: some providers require the client_secret, the default. Some providers will fail
|
|
45
|
+
// if the client secret is passed during that exchange. Set this to true to not send client_secret during the
|
|
46
|
+
// code to token exchange.
|
|
47
|
+
tokenNoSecret: true
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
Microsoft: {
|
|
51
|
+
// Microsoft Azure AD
|
|
52
|
+
client_id: "__enter-your-client-id-here__",
|
|
53
|
+
client_secret: "__enter-your-client-secret-here__",
|
|
54
|
+
|
|
55
|
+
// The settings below are necessary for Microsoft Entra (Azure AD).
|
|
56
|
+
tokenUrlContentType: "application/x-www-form-urlencoded, charset=utf-8",
|
|
57
|
+
tokenNoSecret: true,
|
|
58
|
+
authMethod: "POST"
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
GitHub: {
|
|
62
|
+
client_id: "__enter-your-client-id-here__",
|
|
63
|
+
client_secret: "__enter-your-client-secret-here__",
|
|
64
|
+
// Send the User-Agent header with requests to GitHub.
|
|
65
|
+
"User-Agent": "Profound API"
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
Google: {
|
|
69
|
+
// For a google app, only client_id and client_secret are necessary.
|
|
70
|
+
client_id: "__enter-your-client-id-here__",
|
|
71
|
+
client_secret: "__enter-your-client-secret-here__"
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// The below lines ensure that changes to this config.js can be used without restarting the PJS server.
|
|
77
|
+
// Consequently, this config.js file is read and evaluated every time it is used.
|
|
78
|
+
resolvedMod = require.resolve(__filename);
|
|
79
|
+
delete require.cache[resolvedMod];
|