@lwrjs/auth-middleware 0.9.9-alpha.8 → 0.10.0-alpha.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/README.md +2 -12
- package/build/cjs/utils.cjs +9 -3
- package/build/cjs/web-server.cjs +21 -8
- package/build/es/utils.d.ts +3 -1
- package/build/es/utils.js +10 -3
- package/build/es/web-server.d.ts +3 -0
- package/build/es/web-server.js +21 -8
- package/package.json +8 -8
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@ const lwrApp = createServer(lwrConfig);
|
|
|
23
23
|
platformWebServerAuthMiddleware(lwrApp.getServer()); // Pass in the generic LWR server interface
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
-
> For more information on LWR middleware see [this recipe](https://github.com/salesforce/lwr-recipes/tree/master/packages/custom-middleware).
|
|
26
|
+
> For more information on LWR middleware see [this recipe](https://github.com/salesforce-experience-platform-emu/lwr-recipes/tree/master/packages/custom-middleware).
|
|
27
27
|
|
|
28
28
|
Start the LWR server, and pass the [Connected App information](#variables) in using environment variables:
|
|
29
29
|
|
|
@@ -66,16 +66,6 @@ Before using the LWR authentication middleware, the app developer must set up a
|
|
|
66
66
|
- Make selections for a "Web Server Flow" (not Device or JWT flows).
|
|
67
67
|
- Set the "Callback URL" to the [origin](https://developer.mozilla.org/en-US/docs/Web/API/URL/origin) of the LWR app with an `/oauth` path appended (e.g. `https://website.com/oauth`).
|
|
68
68
|
2. Make a note of the "Consumer Key" (i.e. Client ID) and "Consumer Secret" (i.e. Client Secret) (or find them later in the App Manager).
|
|
69
|
-
3. Let users self-authorize:
|
|
70
|
-
- In Setup, go to "Manage Connected Apps"
|
|
71
|
-
- Click "Edit" next to the new Connected App name
|
|
72
|
-
- Set "Permitted Users" to "All users may self-authorize"
|
|
73
|
-
- Click "Save"
|
|
74
|
-
4. Allow Profiles access to the Connected App:
|
|
75
|
-
- In Setup, go to "Profiles"
|
|
76
|
-
- Click "Edit" next to the desired profile
|
|
77
|
-
- Select the Connected App under "Enable Connected Apps"
|
|
78
|
-
- Click "Save"
|
|
79
69
|
|
|
80
70
|
## Details
|
|
81
71
|
|
|
@@ -128,7 +118,7 @@ The LWR server takes the following steps in order to authenticate and make autho
|
|
|
128
118
|
4. The user sees a login page in their browser and enters their username and password.
|
|
129
119
|
5. The Connected app makes a request to the middleware's redirect URI (i.e. the `/oauth` endpoint) which includes the authorization code.
|
|
130
120
|
6. The middleware POSTs to the org's `/services/oauth2/token` endpoint to request an OAuth token.
|
|
131
|
-
7. Upon receiving the token, the middleware adds it to a [`httpOnly` cookie](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#restrict_access_to_cookies) and redirects to the LWR app (i.e. `app_path`).
|
|
121
|
+
7. Upon receiving the token, the middleware adds it to a secure [`httpOnly` cookie](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#restrict_access_to_cookies) and redirects to the LWR app (i.e. `app_path`).
|
|
132
122
|
8. When the middleware receives a request to the proxy endpoint, it forwards the request to the Salesforce org authorized with the OAuth token:
|
|
133
123
|
|
|
134
124
|
```js
|
package/build/cjs/utils.cjs
CHANGED
|
@@ -10,14 +10,17 @@ __markAsModule(exports);
|
|
|
10
10
|
__export(exports, {
|
|
11
11
|
COOKIE_NAME: () => COOKIE_NAME,
|
|
12
12
|
LOGIN_ENDPOINT: () => LOGIN_ENDPOINT,
|
|
13
|
+
MESSAGE_PREFIX: () => MESSAGE_PREFIX,
|
|
13
14
|
PROXY_ENDPOINT: () => PROXY_ENDPOINT,
|
|
14
15
|
REDIRECT_ENDPOINT: () => REDIRECT_ENDPOINT,
|
|
15
16
|
buildProxyRequest: () => buildProxyRequest,
|
|
16
17
|
buildRedirectUrl: () => buildRedirectUrl,
|
|
17
|
-
|
|
18
|
+
generateStateString: () => generateStateString,
|
|
19
|
+
getAppPath: () => getAppPath,
|
|
18
20
|
getOauthInfoFromCookie: () => getOauthInfoFromCookie,
|
|
19
21
|
getPlatformWebServerEnvVars: () => getPlatformWebServerEnvVars
|
|
20
22
|
});
|
|
23
|
+
var MESSAGE_PREFIX = "Web Server OAuth Middleware - ";
|
|
21
24
|
var LOGIN_ENDPOINT = "/login";
|
|
22
25
|
var REDIRECT_ENDPOINT = "/oauth";
|
|
23
26
|
var PROXY_ENDPOINT = "/services/data";
|
|
@@ -43,13 +46,16 @@ function getPlatformWebServerEnvVars() {
|
|
|
43
46
|
}
|
|
44
47
|
return {myDomain, clientKey, clientSecret};
|
|
45
48
|
}
|
|
46
|
-
function
|
|
49
|
+
function generateStateString() {
|
|
50
|
+
return `s${Math.floor(Math.random() * 65536).toString(16)}`;
|
|
51
|
+
}
|
|
52
|
+
function getAppPath(req) {
|
|
47
53
|
let appPath = req.query[REDIRECT_QUERY];
|
|
48
54
|
if (appPath && !decodeURIComponent(appPath).startsWith("/")) {
|
|
49
55
|
console.warn('The "app_path" parameter must be a relative path staring with "/". Defaulting to "app_path" = "/".');
|
|
50
56
|
appPath = void 0;
|
|
51
57
|
}
|
|
52
|
-
return appPath ?
|
|
58
|
+
return appPath ? decodeURIComponent(appPath) : "/";
|
|
53
59
|
}
|
|
54
60
|
function getOauthInfoFromCookie(req) {
|
|
55
61
|
const oauthInfoStr = req.cookie(COOKIE_NAME);
|
package/build/cjs/web-server.cjs
CHANGED
|
@@ -24,24 +24,34 @@ var __toModule = (module2) => {
|
|
|
24
24
|
// packages/@lwrjs/auth-middleware/src/web-server.ts
|
|
25
25
|
__markAsModule(exports);
|
|
26
26
|
__export(exports, {
|
|
27
|
-
platformWebServerAuthMiddleware: () => platformWebServerAuthMiddleware
|
|
27
|
+
platformWebServerAuthMiddleware: () => platformWebServerAuthMiddleware,
|
|
28
|
+
stateMap: () => stateMap
|
|
28
29
|
});
|
|
29
30
|
var import_node_fetch = __toModule(require("node-fetch"));
|
|
30
31
|
var import_utils = __toModule(require("./utils.cjs"));
|
|
31
|
-
var
|
|
32
|
+
var stateMap = {};
|
|
32
33
|
function platformWebServerAuthMiddleware(server, {proxyEndpoint = import_utils.PROXY_ENDPOINT} = {}) {
|
|
33
34
|
let redirectUri;
|
|
34
35
|
const {myDomain, clientKey, clientSecret} = (0, import_utils.getPlatformWebServerEnvVars)();
|
|
35
36
|
server.get(import_utils.LOGIN_ENDPOINT, (req, res) => {
|
|
36
37
|
redirectUri = redirectUri || (0, import_utils.buildRedirectUrl)(req);
|
|
37
|
-
const
|
|
38
|
+
const stateStr = (0, import_utils.generateStateString)();
|
|
39
|
+
const appPath = (0, import_utils.getAppPath)(req);
|
|
40
|
+
stateMap[stateStr] = appPath;
|
|
38
41
|
const loginUri = `${myDomain}/services/oauth2/authorize?response_type=code`;
|
|
39
|
-
res.set({
|
|
42
|
+
res.set({
|
|
43
|
+
Location: `${loginUri}&client_id=${clientKey}&redirect_uri=${redirectUri}&state=${stateStr}`
|
|
44
|
+
});
|
|
40
45
|
res.sendStatus(302);
|
|
41
46
|
});
|
|
42
47
|
server.get(import_utils.REDIRECT_ENDPOINT, async (req, res) => {
|
|
43
48
|
const {code, state} = req.query;
|
|
44
|
-
const appPath = state
|
|
49
|
+
const appPath = stateMap[state];
|
|
50
|
+
if (!appPath) {
|
|
51
|
+
res.status(400).send(`${import_utils.MESSAGE_PREFIX}invalid login attempt.`);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
delete stateMap[state];
|
|
45
55
|
const response = await (0, import_node_fetch.default)(`${myDomain}/services/oauth2/token`, {
|
|
46
56
|
method: "POST",
|
|
47
57
|
headers: {"Content-Type": "application/x-www-form-urlencoded", Accept: "application/json"},
|
|
@@ -51,11 +61,14 @@ function platformWebServerAuthMiddleware(server, {proxyEndpoint = import_utils.P
|
|
|
51
61
|
const data = await (contentType.includes("application/json") ? response.json() : response.text());
|
|
52
62
|
if (response.ok) {
|
|
53
63
|
const {access_token, instance_url} = data;
|
|
54
|
-
res.cookie(import_utils.COOKIE_NAME, JSON.stringify({access_token, instance_url}), {
|
|
64
|
+
res.cookie(import_utils.COOKIE_NAME, JSON.stringify({access_token, instance_url}), {
|
|
65
|
+
httpOnly: true,
|
|
66
|
+
secure: true
|
|
67
|
+
});
|
|
55
68
|
res.set({Location: appPath});
|
|
56
69
|
res.sendStatus(302);
|
|
57
70
|
} else {
|
|
58
|
-
const errorMsg = `${MESSAGE_PREFIX}could not authenticate.`;
|
|
71
|
+
const errorMsg = `${import_utils.MESSAGE_PREFIX}could not authenticate.`;
|
|
59
72
|
console.error(errorMsg, data);
|
|
60
73
|
res.status(response.status).send(errorMsg);
|
|
61
74
|
}
|
|
@@ -69,7 +82,7 @@ function platformWebServerAuthMiddleware(server, {proxyEndpoint = import_utils.P
|
|
|
69
82
|
const data = await (contentType.includes("application/json") ? proxyRes.json() : proxyRes.text());
|
|
70
83
|
res.status(proxyRes.status).send(data);
|
|
71
84
|
} else {
|
|
72
|
-
res.status(401).send(`${MESSAGE_PREFIX}user is not authenticated.`);
|
|
85
|
+
res.status(401).send(`${import_utils.MESSAGE_PREFIX}user is not authenticated.`);
|
|
73
86
|
}
|
|
74
87
|
});
|
|
75
88
|
}
|
package/build/es/utils.d.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import type { MiddlewareRequest, Headers } from '@lwrjs/types';
|
|
2
2
|
import type { OAuthInfo, RequestOptions, PlatformWebServerInput } from './types.js';
|
|
3
|
+
export declare const MESSAGE_PREFIX = "Web Server OAuth Middleware - ";
|
|
3
4
|
export declare const LOGIN_ENDPOINT = "/login";
|
|
4
5
|
export declare const REDIRECT_ENDPOINT = "/oauth";
|
|
5
6
|
export declare const PROXY_ENDPOINT = "/services/data";
|
|
6
7
|
export declare const COOKIE_NAME = "lwr_web_server_oauth_info";
|
|
7
8
|
export declare function buildRedirectUrl(req: MiddlewareRequest): string;
|
|
8
9
|
export declare function getPlatformWebServerEnvVars(): PlatformWebServerInput;
|
|
9
|
-
export declare function
|
|
10
|
+
export declare function generateStateString(): string;
|
|
11
|
+
export declare function getAppPath(req: MiddlewareRequest): string;
|
|
10
12
|
export declare function getOauthInfoFromCookie(req: MiddlewareRequest): OAuthInfo | undefined;
|
|
11
13
|
export declare function buildProxyRequest(req: MiddlewareRequest, { access_token, instance_url }: OAuthInfo, existingHeader: Headers): RequestOptions;
|
|
12
14
|
//# sourceMappingURL=utils.d.ts.map
|
package/build/es/utils.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export const MESSAGE_PREFIX = 'Web Server OAuth Middleware - ';
|
|
1
2
|
export const LOGIN_ENDPOINT = '/login';
|
|
2
3
|
export const REDIRECT_ENDPOINT = '/oauth';
|
|
3
4
|
export const PROXY_ENDPOINT = '/services/data';
|
|
@@ -27,14 +28,20 @@ export function getPlatformWebServerEnvVars() {
|
|
|
27
28
|
}
|
|
28
29
|
return { myDomain, clientKey, clientSecret };
|
|
29
30
|
}
|
|
30
|
-
//
|
|
31
|
-
|
|
31
|
+
// Create a random string to use as the OAuth state value
|
|
32
|
+
// This value is saved and used to verify the request in the REDIRECT_ENDPOINT
|
|
33
|
+
// See: https://auth0.com/docs/secure/attack-protection/state-parameters
|
|
34
|
+
export function generateStateString() {
|
|
35
|
+
return `s${Math.floor(Math.random() * 0x10000).toString(16)}`;
|
|
36
|
+
}
|
|
37
|
+
// Parse the app_path query param, validate it, and return it decoded. Default = "/"
|
|
38
|
+
export function getAppPath(req) {
|
|
32
39
|
let appPath = req.query[REDIRECT_QUERY];
|
|
33
40
|
if (appPath && !decodeURIComponent(appPath).startsWith('/')) {
|
|
34
41
|
console.warn('The "app_path" parameter must be a relative path staring with "/". Defaulting to "app_path" = "/".');
|
|
35
42
|
appPath = undefined;
|
|
36
43
|
}
|
|
37
|
-
return appPath ?
|
|
44
|
+
return appPath ? decodeURIComponent(appPath) : '/';
|
|
38
45
|
}
|
|
39
46
|
// Get the token and url from the cookie
|
|
40
47
|
export function getOauthInfoFromCookie(req) {
|
package/build/es/web-server.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import type { PublicAppServer, ServerTypes } from '@lwrjs/types';
|
|
2
2
|
import type { PlatformWebServerOptions } from './types.js';
|
|
3
|
+
export declare const stateMap: {
|
|
4
|
+
[key: string]: string;
|
|
5
|
+
};
|
|
3
6
|
/**
|
|
4
7
|
* Web Server OAuth middleware: https://help.salesforce.com/s/articleView?id=sf.remoteaccess_oauth_web_server_flow.htm&type=5
|
|
5
8
|
* Accepts the following environment variables:
|
package/build/es/web-server.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fetch from 'node-fetch';
|
|
2
|
-
import { COOKIE_NAME, LOGIN_ENDPOINT, PROXY_ENDPOINT, REDIRECT_ENDPOINT, buildProxyRequest, buildRedirectUrl,
|
|
3
|
-
const
|
|
2
|
+
import { COOKIE_NAME, LOGIN_ENDPOINT, MESSAGE_PREFIX, PROXY_ENDPOINT, REDIRECT_ENDPOINT, buildProxyRequest, buildRedirectUrl, generateStateString, getAppPath, getOauthInfoFromCookie, getPlatformWebServerEnvVars, } from './utils.js';
|
|
3
|
+
export const stateMap = {};
|
|
4
4
|
/**
|
|
5
5
|
* Web Server OAuth middleware: https://help.salesforce.com/s/articleView?id=sf.remoteaccess_oauth_web_server_flow.htm&type=5
|
|
6
6
|
* Accepts the following environment variables:
|
|
@@ -16,20 +16,30 @@ export function platformWebServerAuthMiddleware(server, { proxyEndpoint = PROXY_
|
|
|
16
16
|
// First, request an authorization code by redirecting to Salesforce login
|
|
17
17
|
// The LWR app should call this endpoint when authentication is needed
|
|
18
18
|
server.get(LOGIN_ENDPOINT, (req, res) => {
|
|
19
|
-
// Build the redirect URI and save the app path in the
|
|
19
|
+
// Build the redirect URI and save the app path in the state map
|
|
20
20
|
redirectUri = redirectUri || buildRedirectUrl(req);
|
|
21
|
-
const
|
|
21
|
+
const stateStr = generateStateString();
|
|
22
|
+
const appPath = getAppPath(req);
|
|
23
|
+
stateMap[stateStr] = appPath; // Save the state string for verification in step 2
|
|
22
24
|
// Redirect to the Salesforce login page
|
|
23
25
|
const loginUri = `${myDomain}/services/oauth2/authorize?response_type=code`;
|
|
24
|
-
res.set({
|
|
26
|
+
res.set({
|
|
27
|
+
Location: `${loginUri}&client_id=${clientKey}&redirect_uri=${redirectUri}&state=${stateStr}`,
|
|
28
|
+
});
|
|
25
29
|
res.sendStatus(302);
|
|
26
30
|
});
|
|
27
31
|
// Second, use the authorization code from the redirect URI to retrieve the token
|
|
28
32
|
// This route MUST BE the "Callback URL" in the Connected App OAuth settings
|
|
29
33
|
server.get(REDIRECT_ENDPOINT, async (req, res) => {
|
|
30
|
-
// Parse the code and state
|
|
34
|
+
// Parse the code and state string from the query params
|
|
31
35
|
const { code, state } = req.query;
|
|
32
|
-
const appPath = state
|
|
36
|
+
const appPath = stateMap[state];
|
|
37
|
+
// If the state string is not found in the map, the request is invalid
|
|
38
|
+
if (!appPath) {
|
|
39
|
+
res.status(400).send(`${MESSAGE_PREFIX}invalid login attempt.`);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
delete stateMap[state];
|
|
33
43
|
// Fetch the OAuth token
|
|
34
44
|
const response = await fetch(`${myDomain}/services/oauth2/token`, {
|
|
35
45
|
method: 'POST',
|
|
@@ -44,7 +54,10 @@ export function platformWebServerAuthMiddleware(server, { proxyEndpoint = PROXY_
|
|
|
44
54
|
if (response.ok) {
|
|
45
55
|
// Store the OAuth info in a cookie and redirect to the app
|
|
46
56
|
const { access_token, instance_url } = data;
|
|
47
|
-
res.cookie(COOKIE_NAME, JSON.stringify({ access_token, instance_url }), {
|
|
57
|
+
res.cookie(COOKIE_NAME, JSON.stringify({ access_token, instance_url }), {
|
|
58
|
+
httpOnly: true,
|
|
59
|
+
secure: true,
|
|
60
|
+
});
|
|
48
61
|
res.set({ Location: appPath });
|
|
49
62
|
res.sendStatus(302);
|
|
50
63
|
}
|
package/package.json
CHANGED
|
@@ -4,15 +4,15 @@
|
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
7
|
-
"version": "0.
|
|
7
|
+
"version": "0.10.0-alpha.1",
|
|
8
8
|
"homepage": "https://developer.salesforce.com/docs/platform/lwr/overview",
|
|
9
9
|
"repository": {
|
|
10
10
|
"type": "git",
|
|
11
|
-
"url": "https://github.com/salesforce/lwr.git",
|
|
11
|
+
"url": "https://github.com/salesforce-experience-platform-emu/lwr.git",
|
|
12
12
|
"directory": "packages/@lwrjs/auth-middleware"
|
|
13
13
|
},
|
|
14
14
|
"bugs": {
|
|
15
|
-
"url": "https://github.com/salesforce/lwr/issues"
|
|
15
|
+
"url": "https://github.com/salesforce-experience-platform-emu/lwr/issues"
|
|
16
16
|
},
|
|
17
17
|
"type": "module",
|
|
18
18
|
"types": "build/es/index.d.ts",
|
|
@@ -30,15 +30,15 @@
|
|
|
30
30
|
"build/**/*.d.ts"
|
|
31
31
|
],
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"node-fetch": "^2.6.
|
|
33
|
+
"node-fetch": "^2.6.8"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
|
-
"@lwrjs/server": "0.
|
|
37
|
-
"@lwrjs/types": "0.
|
|
36
|
+
"@lwrjs/server": "0.10.0-alpha.1",
|
|
37
|
+
"@lwrjs/types": "0.10.0-alpha.1",
|
|
38
38
|
"@types/node-fetch": "^2.5.12"
|
|
39
39
|
},
|
|
40
40
|
"engines": {
|
|
41
|
-
"node": ">=
|
|
41
|
+
"node": ">=16.0.0 <20"
|
|
42
42
|
},
|
|
43
|
-
"gitHead": "
|
|
43
|
+
"gitHead": "2683dd5839b4da2ed93d42874cfceb78cbb5bf0d"
|
|
44
44
|
}
|