@lwrjs/auth-middleware 0.10.0-alpha.3 → 0.10.0-alpha.4
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 +8 -2
- package/build/cjs/utils.cjs +12 -12
- package/build/cjs/web-server.cjs +33 -14
- package/build/es/types.d.ts +12 -2
- package/build/es/utils.d.ts +3 -1
- package/build/es/utils.js +10 -13
- package/build/es/web-server.d.ts +7 -5
- package/build/es/web-server.js +43 -17
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -28,7 +28,7 @@ platformWebServerAuthMiddleware(lwrApp.getServer()); // Pass in the generic LWR
|
|
|
28
28
|
Start the LWR server, and pass the [Connected App information](#variables) in using environment variables:
|
|
29
29
|
|
|
30
30
|
```
|
|
31
|
-
|
|
31
|
+
CLIENT_KEY=abc.123 CLIENT_SECRET=****** yarn start
|
|
32
32
|
```
|
|
33
33
|
|
|
34
34
|
Authenticate by accessing the `/login` [endpoint](#endpoints) from the LWR app:
|
|
@@ -91,7 +91,6 @@ platformWebServerAuthMiddleware(lwrApp.getServer(), { proxyEndpoint: '/some/wher
|
|
|
91
91
|
|
|
92
92
|
In order to authenticate with the Web Server Flow, the following Connected App information is needed:
|
|
93
93
|
|
|
94
|
-
- `MY_DOMAIN`: The [domain of the Salesforce org](https://help.salesforce.com/s/articleView?id=sf.faq_domain_name_what.htm&type=5) (_Note_: Find this in Setup -> "My Domain"; this is **not** necessarily the same as the URL in the browser address bar when visiting the org).
|
|
95
94
|
- `CLIENT_KEY`: The public OAuth identifier for the Connected App (i.e. "Consumer Key", "Client ID").
|
|
96
95
|
- `CLIENT_SECRET`: The password to go along with the `CLIENT_KEY` (i.e. "Consumer Secret", "Client Secret"), known only to the Connected App the and LWR server.
|
|
97
96
|
|
|
@@ -99,6 +98,12 @@ This information is passed to the LWR server via environment variables to keep t
|
|
|
99
98
|
|
|
100
99
|
> Read more about the OAuth Client ID and Secret [here](https://www.oauth.com/oauth2-servers/client-registration/client-id-secret/).
|
|
101
100
|
|
|
101
|
+
#### Optional Variables
|
|
102
|
+
|
|
103
|
+
Additionally, the following optional environment variables are supported:
|
|
104
|
+
|
|
105
|
+
- `MY_DOMAIN`: The [domain of the Salesforce org](https://help.salesforce.com/s/articleView?id=sf.faq_domain_name_what.htm&type=5) (_Note_: Find this in Setup -> "My Domain"; this is **not** necessarily the same as the URL in the browser address bar when visiting the org). If this is not set then `https://login.salesforce.com` will be used as the fallback login server. NOTE: if a login request has the `login_server` query parameter set that overrides `MY_DOMAIN` for that login request.
|
|
106
|
+
|
|
102
107
|
### Endpoints
|
|
103
108
|
|
|
104
109
|
The LWR authentication middleware provides the following endpoints:
|
|
@@ -106,6 +111,7 @@ The LWR authentication middleware provides the following endpoints:
|
|
|
106
111
|
- `/login`: Accessed from the LWR app client code to trigger the OAuth flow and allow the current user to log in.
|
|
107
112
|
- The `/login` endpoint takes an optional query parameter: `app_path`. Set `app_path` to the [path and parameters](https://developer.mozilla.org/en-US/docs/Learn/Common_questions/What_is_a_URL#path_to_resource) the LWR server should redirect to after authenticating (e.g. `/home`, `/list?sort=desc`). If omitted, `app_path` defaults to `/`. This value must be URL encoded.
|
|
108
113
|
- `/oauth`: Used internally as part of the OAuth flow, this is the [OAuth redirect URI](https://www.oauth.com/oauth2-servers/redirect-uris/) for the [Connected App](#connected-app-setup) which fetches the OAuth token.
|
|
114
|
+
- `/revoke`: Accessed from the LWR app client code to trigger a revoke of the access token. This should be called whenever a user logs out of the LWR app client.
|
|
109
115
|
- proxy endpoint: Requests sent to this endpoint are proxied to the Salesforce org with the OAuth token in an [Authorization header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization). By proxying these requests through the LWR server, the token remains secure from inspection by the client. The proxy endpoint is optionally passed into the [`platformWebServerAuthMiddleware`](#middleware) as an option; the default proxy endpoint is `/services/data`.
|
|
110
116
|
|
|
111
117
|
### Flow
|
package/build/cjs/utils.cjs
CHANGED
|
@@ -9,25 +9,28 @@ var __export = (target, all) => {
|
|
|
9
9
|
__markAsModule(exports);
|
|
10
10
|
__export(exports, {
|
|
11
11
|
COOKIE_NAME: () => COOKIE_NAME,
|
|
12
|
+
DEFAULT_LOGIN_SERVER: () => DEFAULT_LOGIN_SERVER,
|
|
12
13
|
LOGIN_ENDPOINT: () => LOGIN_ENDPOINT,
|
|
13
14
|
MESSAGE_PREFIX: () => MESSAGE_PREFIX,
|
|
14
15
|
PROXY_ENDPOINT: () => PROXY_ENDPOINT,
|
|
15
16
|
REDIRECT_ENDPOINT: () => REDIRECT_ENDPOINT,
|
|
17
|
+
REVOKE_ENDPOINT: () => REVOKE_ENDPOINT,
|
|
16
18
|
buildProxyRequest: () => buildProxyRequest,
|
|
17
19
|
buildRedirectUrl: () => buildRedirectUrl,
|
|
18
20
|
generateStateString: () => generateStateString,
|
|
19
|
-
getAppPath: () => getAppPath,
|
|
20
21
|
getOauthInfoFromCookie: () => getOauthInfoFromCookie,
|
|
21
|
-
getPlatformWebServerEnvVars: () => getPlatformWebServerEnvVars
|
|
22
|
+
getPlatformWebServerEnvVars: () => getPlatformWebServerEnvVars,
|
|
23
|
+
sanitizeAppPath: () => sanitizeAppPath
|
|
22
24
|
});
|
|
23
25
|
var MESSAGE_PREFIX = "Web Server OAuth Middleware - ";
|
|
24
26
|
var LOGIN_ENDPOINT = "/login";
|
|
27
|
+
var REVOKE_ENDPOINT = "/revoke";
|
|
25
28
|
var REDIRECT_ENDPOINT = "/oauth";
|
|
26
29
|
var PROXY_ENDPOINT = "/services/data";
|
|
27
30
|
var COOKIE_NAME = "lwr_web_server_oauth_info";
|
|
28
|
-
var
|
|
31
|
+
var DEFAULT_LOGIN_SERVER = "https://login.salesforce.com";
|
|
29
32
|
function buildRedirectUrl(req) {
|
|
30
|
-
return
|
|
33
|
+
return `${req.protocol}://${req.get("host")}${REDIRECT_ENDPOINT}`;
|
|
31
34
|
}
|
|
32
35
|
function getPlatformWebServerEnvVars() {
|
|
33
36
|
const env = {myDomain: "MY_DOMAIN", clientKey: "CLIENT_KEY", clientSecret: "CLIENT_SECRET"};
|
|
@@ -35,9 +38,6 @@ function getPlatformWebServerEnvVars() {
|
|
|
35
38
|
const clientKey = process.env[env.clientKey];
|
|
36
39
|
const clientSecret = process.env[env.clientSecret];
|
|
37
40
|
const errorMsg = "Web Server OAuth Middleware: missing process.env.";
|
|
38
|
-
if (!myDomain) {
|
|
39
|
-
throw new Error(`${errorMsg}${env.myDomain}`);
|
|
40
|
-
}
|
|
41
41
|
if (!clientKey) {
|
|
42
42
|
throw new Error(`${errorMsg}${env.clientKey}`);
|
|
43
43
|
}
|
|
@@ -49,13 +49,13 @@ function getPlatformWebServerEnvVars() {
|
|
|
49
49
|
function generateStateString() {
|
|
50
50
|
return `s${Math.floor(Math.random() * 65536).toString(16)}`;
|
|
51
51
|
}
|
|
52
|
-
function
|
|
53
|
-
let appPath =
|
|
54
|
-
if (
|
|
52
|
+
function sanitizeAppPath(app_path) {
|
|
53
|
+
let appPath = app_path ? decodeURIComponent(app_path) : `/`;
|
|
54
|
+
if (!appPath.startsWith("/")) {
|
|
55
55
|
console.warn('The "app_path" parameter must be a relative path staring with "/". Defaulting to "app_path" = "/".');
|
|
56
|
-
appPath =
|
|
56
|
+
appPath = "/";
|
|
57
57
|
}
|
|
58
|
-
return appPath
|
|
58
|
+
return appPath;
|
|
59
59
|
}
|
|
60
60
|
function getOauthInfoFromCookie(req) {
|
|
61
61
|
const oauthInfoStr = req.cookie(COOKIE_NAME);
|
package/build/cjs/web-server.cjs
CHANGED
|
@@ -29,33 +29,36 @@ __export(exports, {
|
|
|
29
29
|
});
|
|
30
30
|
var import_node_fetch = __toModule(require("node-fetch"));
|
|
31
31
|
var import_utils = __toModule(require("./utils.cjs"));
|
|
32
|
-
var stateMap =
|
|
32
|
+
var stateMap = new Map();
|
|
33
33
|
function platformWebServerAuthMiddleware(server, {proxyEndpoint = import_utils.PROXY_ENDPOINT} = {}) {
|
|
34
|
-
|
|
35
|
-
const
|
|
34
|
+
const {clientKey, clientSecret, myDomain} = (0, import_utils.getPlatformWebServerEnvVars)();
|
|
35
|
+
const loginServerFallback = myDomain || import_utils.DEFAULT_LOGIN_SERVER;
|
|
36
36
|
server.get(import_utils.LOGIN_ENDPOINT, (req, res) => {
|
|
37
|
-
|
|
37
|
+
const {redirect_uri, login_server, app_path} = req.query;
|
|
38
|
+
const redirectUri = redirect_uri ? decodeURIComponent(redirect_uri) : (0, import_utils.buildRedirectUrl)(req);
|
|
39
|
+
const loginServer = login_server ? decodeURIComponent(login_server) : loginServerFallback;
|
|
40
|
+
const appPath = (0, import_utils.sanitizeAppPath)(app_path);
|
|
38
41
|
const stateStr = (0, import_utils.generateStateString)();
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
const loginUri = `${myDomain}/services/oauth2/authorize?response_type=code`;
|
|
42
|
+
stateMap.set(stateStr, {redirectUri, loginServer, appPath});
|
|
43
|
+
const loginUri = `${loginServer}/services/oauth2/authorize?response_type=code`;
|
|
42
44
|
res.set({
|
|
43
|
-
Location: `${loginUri}&client_id=${clientKey}&redirect_uri=${redirectUri}&state=${stateStr}`
|
|
45
|
+
Location: `${loginUri}&client_id=${clientKey}&redirect_uri=${encodeURIComponent(redirectUri)}&state=${stateStr}`
|
|
44
46
|
});
|
|
45
47
|
res.sendStatus(302);
|
|
46
48
|
});
|
|
47
49
|
server.get(import_utils.REDIRECT_ENDPOINT, async (req, res) => {
|
|
48
50
|
const {code, state} = req.query;
|
|
49
|
-
const
|
|
50
|
-
if (!
|
|
51
|
+
const loginState = stateMap.get(state);
|
|
52
|
+
if (!loginState) {
|
|
51
53
|
res.status(400).send(`${import_utils.MESSAGE_PREFIX}invalid login attempt.`);
|
|
52
54
|
return;
|
|
53
55
|
}
|
|
54
|
-
delete
|
|
55
|
-
const
|
|
56
|
+
stateMap.delete(state);
|
|
57
|
+
const {appPath, loginServer, redirectUri} = loginState;
|
|
58
|
+
const response = await (0, import_node_fetch.default)(`${loginServer}/services/oauth2/token`, {
|
|
56
59
|
method: "POST",
|
|
57
60
|
headers: {"Content-Type": "application/x-www-form-urlencoded", Accept: "application/json"},
|
|
58
|
-
body: `grant_type=authorization_code&code=${code}&client_id=${clientKey}&client_secret=${clientSecret}&redirect_uri=${redirectUri}`
|
|
61
|
+
body: `grant_type=authorization_code&code=${code}&client_id=${clientKey}&client_secret=${clientSecret}&redirect_uri=${encodeURIComponent(redirectUri)}`
|
|
59
62
|
});
|
|
60
63
|
const contentType = response.headers.get("Content-Type") || "";
|
|
61
64
|
const data = await (contentType.includes("application/json") ? response.json() : response.text());
|
|
@@ -68,7 +71,7 @@ function platformWebServerAuthMiddleware(server, {proxyEndpoint = import_utils.P
|
|
|
68
71
|
res.set({Location: appPath});
|
|
69
72
|
res.sendStatus(302);
|
|
70
73
|
} else {
|
|
71
|
-
const errorMsg = `${import_utils.MESSAGE_PREFIX}could not authenticate
|
|
74
|
+
const errorMsg = `${import_utils.MESSAGE_PREFIX}could not authenticate. Error body: ${JSON.stringify(data)}`;
|
|
72
75
|
console.error(errorMsg, data);
|
|
73
76
|
res.status(response.status).send(errorMsg);
|
|
74
77
|
}
|
|
@@ -85,4 +88,20 @@ function platformWebServerAuthMiddleware(server, {proxyEndpoint = import_utils.P
|
|
|
85
88
|
res.status(401).send(`${import_utils.MESSAGE_PREFIX}user is not authenticated.`);
|
|
86
89
|
}
|
|
87
90
|
});
|
|
91
|
+
server.get(import_utils.REVOKE_ENDPOINT, async (req, res) => {
|
|
92
|
+
let returnStatus = 200;
|
|
93
|
+
let returnBody = null;
|
|
94
|
+
const oauthInfo = (0, import_utils.getOauthInfoFromCookie)(req);
|
|
95
|
+
if (oauthInfo) {
|
|
96
|
+
const {access_token, instance_url} = oauthInfo;
|
|
97
|
+
const url = `${instance_url}/services/oauth2/revoke?token=${access_token}`;
|
|
98
|
+
const proxyRes = await (0, import_node_fetch.default)(url);
|
|
99
|
+
const contentType = proxyRes.headers.get("Content-Type") || "";
|
|
100
|
+
const data = await (contentType.includes("application/json") ? proxyRes.json() : proxyRes.text());
|
|
101
|
+
returnStatus = proxyRes.status;
|
|
102
|
+
returnBody = data;
|
|
103
|
+
}
|
|
104
|
+
res.cookie(import_utils.COOKIE_NAME, "", {httpOnly: true, secure: true});
|
|
105
|
+
res.status(returnStatus).send(returnBody);
|
|
106
|
+
});
|
|
88
107
|
}
|
package/build/es/types.d.ts
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import type { RequestInit } from 'node-fetch';
|
|
2
2
|
export interface PlatformWebServerInput {
|
|
3
|
-
/** base URL to the Connected App, e.g. "https://lwr.lightning.force.com" (no trailing slash) */
|
|
4
|
-
myDomain: string;
|
|
5
3
|
/** "Consumer Key" from the Connected App */
|
|
6
4
|
clientKey: string;
|
|
7
5
|
/** "Consumer Secret" from the Connected App */
|
|
8
6
|
clientSecret: string;
|
|
7
|
+
/**
|
|
8
|
+
* (optional) base URL to the Connected App, e.g. "https://lwr.lightning.force.com" (no trailing slash)
|
|
9
|
+
*
|
|
10
|
+
* This is not used if a request sets the "login_server" query parameter. Login will
|
|
11
|
+
* default to "https://login.salesforce.com" if this is not set.
|
|
12
|
+
*/
|
|
13
|
+
myDomain?: string;
|
|
9
14
|
}
|
|
10
15
|
export interface PlatformWebServerOptions {
|
|
11
16
|
/** The endpoint path through which all authenticated/proxied requests pass */
|
|
@@ -21,4 +26,9 @@ export interface RequestOptions {
|
|
|
21
26
|
url: string;
|
|
22
27
|
options: RequestInit;
|
|
23
28
|
}
|
|
29
|
+
export interface LoginState {
|
|
30
|
+
appPath: string;
|
|
31
|
+
redirectUri: string;
|
|
32
|
+
loginServer: string;
|
|
33
|
+
}
|
|
24
34
|
//# sourceMappingURL=types.d.ts.map
|
package/build/es/utils.d.ts
CHANGED
|
@@ -2,13 +2,15 @@ import type { MiddlewareRequest, Headers } from '@lwrjs/types';
|
|
|
2
2
|
import type { OAuthInfo, RequestOptions, PlatformWebServerInput } from './types.js';
|
|
3
3
|
export declare const MESSAGE_PREFIX = "Web Server OAuth Middleware - ";
|
|
4
4
|
export declare const LOGIN_ENDPOINT = "/login";
|
|
5
|
+
export declare const REVOKE_ENDPOINT = "/revoke";
|
|
5
6
|
export declare const REDIRECT_ENDPOINT = "/oauth";
|
|
6
7
|
export declare const PROXY_ENDPOINT = "/services/data";
|
|
7
8
|
export declare const COOKIE_NAME = "lwr_web_server_oauth_info";
|
|
9
|
+
export declare const DEFAULT_LOGIN_SERVER = "https://login.salesforce.com";
|
|
8
10
|
export declare function buildRedirectUrl(req: MiddlewareRequest): string;
|
|
9
11
|
export declare function getPlatformWebServerEnvVars(): PlatformWebServerInput;
|
|
10
12
|
export declare function generateStateString(): string;
|
|
11
|
-
export declare function
|
|
13
|
+
export declare function sanitizeAppPath(app_path: string | undefined): string;
|
|
12
14
|
export declare function getOauthInfoFromCookie(req: MiddlewareRequest): OAuthInfo | undefined;
|
|
13
15
|
export declare function buildProxyRequest(req: MiddlewareRequest, { access_token, instance_url }: OAuthInfo, existingHeader: Headers): RequestOptions;
|
|
14
16
|
//# sourceMappingURL=utils.d.ts.map
|
package/build/es/utils.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
export const MESSAGE_PREFIX = 'Web Server OAuth Middleware - ';
|
|
2
2
|
export const LOGIN_ENDPOINT = '/login';
|
|
3
|
+
export const REVOKE_ENDPOINT = '/revoke';
|
|
3
4
|
export const REDIRECT_ENDPOINT = '/oauth';
|
|
4
5
|
export const PROXY_ENDPOINT = '/services/data';
|
|
5
6
|
export const COOKIE_NAME = 'lwr_web_server_oauth_info';
|
|
6
|
-
const
|
|
7
|
+
export const DEFAULT_LOGIN_SERVER = 'https://login.salesforce.com';
|
|
7
8
|
// Create an OAuth redirect URI from the origin of a request
|
|
8
|
-
// Return the redirect URI encoded so it can be used as a query param
|
|
9
9
|
export function buildRedirectUrl(req) {
|
|
10
|
-
return
|
|
10
|
+
return `${req.protocol}://${req.get('host')}${REDIRECT_ENDPOINT}`;
|
|
11
11
|
}
|
|
12
12
|
// Retrieve and validate environment variables for the Web Server OAuth flow
|
|
13
13
|
export function getPlatformWebServerEnvVars() {
|
|
@@ -15,11 +15,8 @@ export function getPlatformWebServerEnvVars() {
|
|
|
15
15
|
const myDomain = process.env[env.myDomain];
|
|
16
16
|
const clientKey = process.env[env.clientKey];
|
|
17
17
|
const clientSecret = process.env[env.clientSecret];
|
|
18
|
-
//
|
|
18
|
+
// Check required vars
|
|
19
19
|
const errorMsg = 'Web Server OAuth Middleware: missing process.env.';
|
|
20
|
-
if (!myDomain) {
|
|
21
|
-
throw new Error(`${errorMsg}${env.myDomain}`);
|
|
22
|
-
}
|
|
23
20
|
if (!clientKey) {
|
|
24
21
|
throw new Error(`${errorMsg}${env.clientKey}`);
|
|
25
22
|
}
|
|
@@ -34,14 +31,14 @@ export function getPlatformWebServerEnvVars() {
|
|
|
34
31
|
export function generateStateString() {
|
|
35
32
|
return `s${Math.floor(Math.random() * 0x10000).toString(16)}`;
|
|
36
33
|
}
|
|
37
|
-
//
|
|
38
|
-
export function
|
|
39
|
-
let appPath =
|
|
40
|
-
if (
|
|
34
|
+
// Ensures the given appPath is defined and decoded. Default = "/"
|
|
35
|
+
export function sanitizeAppPath(app_path) {
|
|
36
|
+
let appPath = app_path ? decodeURIComponent(app_path) : `/`;
|
|
37
|
+
if (!appPath.startsWith('/')) {
|
|
41
38
|
console.warn('The "app_path" parameter must be a relative path staring with "/". Defaulting to "app_path" = "/".');
|
|
42
|
-
appPath =
|
|
39
|
+
appPath = '/';
|
|
43
40
|
}
|
|
44
|
-
return appPath
|
|
41
|
+
return appPath;
|
|
45
42
|
}
|
|
46
43
|
// Get the token and url from the cookie
|
|
47
44
|
export function getOauthInfoFromCookie(req) {
|
package/build/es/web-server.d.ts
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
import type { PublicAppServer, ServerTypes } from '@lwrjs/types';
|
|
2
|
-
import type { PlatformWebServerOptions } from './types.js';
|
|
3
|
-
export declare const stateMap:
|
|
4
|
-
[key: string]: string;
|
|
5
|
-
};
|
|
2
|
+
import type { PlatformWebServerOptions, LoginState } from './types.js';
|
|
3
|
+
export declare const stateMap: Map<string, LoginState>;
|
|
6
4
|
/**
|
|
7
5
|
* Web Server OAuth middleware: https://help.salesforce.com/s/articleView?id=sf.remoteaccess_oauth_web_server_flow.htm&type=5
|
|
8
6
|
* Accepts the following environment variables:
|
|
9
|
-
* - process.env.MY_DOMAIN
|
|
10
7
|
* - process.env.CLIENT_KEY
|
|
11
8
|
* - process.env.CLIENT_SECRET
|
|
9
|
+
* - process.env.MY_DOMAIN: (optional) The domain of the salesforce org, will be used to determine the login server if
|
|
10
|
+
* login_server query parameter is not set. Login server defaults to "https://login.salesforce.com"
|
|
11
|
+
* if both login_server nd MY_DOMAIN are not set.
|
|
12
12
|
* The login endpoint accepts the following query parameters:
|
|
13
13
|
* - app_path: a path encoded string; the middleware redirects to this path after setting the OAuth token in a cookie; default is "/"
|
|
14
|
+
* - login_server: an encoded string; the middleware attempts to login at this address; default is "https://login.salesforce.com"
|
|
15
|
+
* - redirect_uri: an encoded string; this is where the middleware redirects to after OAuth is done; default is calculated from the request source
|
|
14
16
|
*/
|
|
15
17
|
export declare function platformWebServerAuthMiddleware<T extends ServerTypes>(server: PublicAppServer<T>, { proxyEndpoint }?: PlatformWebServerOptions): void;
|
|
16
18
|
//# sourceMappingURL=web-server.d.ts.map
|
package/build/es/web-server.js
CHANGED
|
@@ -1,30 +1,37 @@
|
|
|
1
1
|
import fetch from 'node-fetch';
|
|
2
|
-
import { COOKIE_NAME, LOGIN_ENDPOINT, MESSAGE_PREFIX, PROXY_ENDPOINT, REDIRECT_ENDPOINT, buildProxyRequest, buildRedirectUrl, generateStateString,
|
|
3
|
-
export const stateMap =
|
|
2
|
+
import { COOKIE_NAME, DEFAULT_LOGIN_SERVER, LOGIN_ENDPOINT, MESSAGE_PREFIX, PROXY_ENDPOINT, REDIRECT_ENDPOINT, REVOKE_ENDPOINT, buildProxyRequest, buildRedirectUrl, generateStateString, sanitizeAppPath, getOauthInfoFromCookie, getPlatformWebServerEnvVars, } from './utils.js';
|
|
3
|
+
export const stateMap = new Map();
|
|
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:
|
|
7
|
-
* - process.env.MY_DOMAIN
|
|
8
7
|
* - process.env.CLIENT_KEY
|
|
9
8
|
* - process.env.CLIENT_SECRET
|
|
9
|
+
* - process.env.MY_DOMAIN: (optional) The domain of the salesforce org, will be used to determine the login server if
|
|
10
|
+
* login_server query parameter is not set. Login server defaults to "https://login.salesforce.com"
|
|
11
|
+
* if both login_server nd MY_DOMAIN are not set.
|
|
10
12
|
* The login endpoint accepts the following query parameters:
|
|
11
13
|
* - app_path: a path encoded string; the middleware redirects to this path after setting the OAuth token in a cookie; default is "/"
|
|
14
|
+
* - login_server: an encoded string; the middleware attempts to login at this address; default is "https://login.salesforce.com"
|
|
15
|
+
* - redirect_uri: an encoded string; this is where the middleware redirects to after OAuth is done; default is calculated from the request source
|
|
12
16
|
*/
|
|
13
17
|
export function platformWebServerAuthMiddleware(server, { proxyEndpoint = PROXY_ENDPOINT } = {}) {
|
|
14
|
-
|
|
15
|
-
const
|
|
18
|
+
const { clientKey, clientSecret, myDomain } = getPlatformWebServerEnvVars();
|
|
19
|
+
const loginServerFallback = myDomain || DEFAULT_LOGIN_SERVER;
|
|
16
20
|
// First, request an authorization code by redirecting to Salesforce login
|
|
17
21
|
// The LWR app should call this endpoint when authentication is needed
|
|
18
22
|
server.get(LOGIN_ENDPOINT, (req, res) => {
|
|
19
|
-
//
|
|
20
|
-
|
|
23
|
+
// Parse the encoded redirect_uri, login_server, app_path query params
|
|
24
|
+
const { redirect_uri, login_server, app_path } = req.query;
|
|
25
|
+
const redirectUri = redirect_uri ? decodeURIComponent(redirect_uri) : buildRedirectUrl(req);
|
|
26
|
+
const loginServer = login_server ? decodeURIComponent(login_server) : loginServerFallback;
|
|
27
|
+
const appPath = sanitizeAppPath(app_path);
|
|
28
|
+
// Save the state string for verification in step 2
|
|
21
29
|
const stateStr = generateStateString();
|
|
22
|
-
|
|
23
|
-
stateMap[stateStr] = appPath; // Save the state string for verification in step 2
|
|
30
|
+
stateMap.set(stateStr, { redirectUri, loginServer, appPath });
|
|
24
31
|
// Redirect to the Salesforce login page
|
|
25
|
-
const loginUri = `${
|
|
32
|
+
const loginUri = `${loginServer}/services/oauth2/authorize?response_type=code`;
|
|
26
33
|
res.set({
|
|
27
|
-
Location: `${loginUri}&client_id=${clientKey}&redirect_uri=${redirectUri}&state=${stateStr}`,
|
|
34
|
+
Location: `${loginUri}&client_id=${clientKey}&redirect_uri=${encodeURIComponent(redirectUri)}&state=${stateStr}`,
|
|
28
35
|
});
|
|
29
36
|
res.sendStatus(302);
|
|
30
37
|
});
|
|
@@ -33,18 +40,19 @@ export function platformWebServerAuthMiddleware(server, { proxyEndpoint = PROXY_
|
|
|
33
40
|
server.get(REDIRECT_ENDPOINT, async (req, res) => {
|
|
34
41
|
// Parse the code and state string from the query params
|
|
35
42
|
const { code, state } = req.query;
|
|
36
|
-
const
|
|
43
|
+
const loginState = stateMap.get(state);
|
|
37
44
|
// If the state string is not found in the map, the request is invalid
|
|
38
|
-
if (!
|
|
45
|
+
if (!loginState) {
|
|
39
46
|
res.status(400).send(`${MESSAGE_PREFIX}invalid login attempt.`);
|
|
40
47
|
return;
|
|
41
48
|
}
|
|
42
|
-
delete
|
|
49
|
+
stateMap.delete(state);
|
|
50
|
+
const { appPath, loginServer, redirectUri } = loginState;
|
|
43
51
|
// Fetch the OAuth token
|
|
44
|
-
const response = await fetch(`${
|
|
52
|
+
const response = await fetch(`${loginServer}/services/oauth2/token`, {
|
|
45
53
|
method: 'POST',
|
|
46
54
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded', Accept: 'application/json' },
|
|
47
|
-
body: `grant_type=authorization_code&code=${code}&client_id=${clientKey}&client_secret=${clientSecret}&redirect_uri=${redirectUri}`,
|
|
55
|
+
body: `grant_type=authorization_code&code=${code}&client_id=${clientKey}&client_secret=${clientSecret}&redirect_uri=${encodeURIComponent(redirectUri)}`,
|
|
48
56
|
});
|
|
49
57
|
// Response handling
|
|
50
58
|
const contentType = response.headers.get('Content-Type') || ''; // parse as json or text
|
|
@@ -63,7 +71,7 @@ export function platformWebServerAuthMiddleware(server, { proxyEndpoint = PROXY_
|
|
|
63
71
|
}
|
|
64
72
|
else {
|
|
65
73
|
// Print and forward the error JSON to the client
|
|
66
|
-
const errorMsg = `${MESSAGE_PREFIX}could not authenticate
|
|
74
|
+
const errorMsg = `${MESSAGE_PREFIX}could not authenticate. Error body: ${JSON.stringify(data)}`;
|
|
67
75
|
console.error(errorMsg, data);
|
|
68
76
|
res.status(response.status).send(errorMsg);
|
|
69
77
|
}
|
|
@@ -83,5 +91,23 @@ export function platformWebServerAuthMiddleware(server, { proxyEndpoint = PROXY_
|
|
|
83
91
|
res.status(401).send(`${MESSAGE_PREFIX}user is not authenticated.`);
|
|
84
92
|
}
|
|
85
93
|
});
|
|
94
|
+
// Revoke token if user explicitly logs out
|
|
95
|
+
server.get(REVOKE_ENDPOINT, async (req, res) => {
|
|
96
|
+
let returnStatus = 200;
|
|
97
|
+
let returnBody = null;
|
|
98
|
+
const oauthInfo = getOauthInfoFromCookie(req);
|
|
99
|
+
if (oauthInfo) {
|
|
100
|
+
const { access_token, instance_url } = oauthInfo;
|
|
101
|
+
const url = `${instance_url}/services/oauth2/revoke?token=${access_token}`;
|
|
102
|
+
const proxyRes = await fetch(url);
|
|
103
|
+
const contentType = proxyRes.headers.get('Content-Type') || ''; // parse as json or text
|
|
104
|
+
const data = await (contentType.includes('application/json') ? proxyRes.json() : proxyRes.text());
|
|
105
|
+
returnStatus = proxyRes.status;
|
|
106
|
+
returnBody = data;
|
|
107
|
+
}
|
|
108
|
+
// ensure cookie is cleared
|
|
109
|
+
res.cookie(COOKIE_NAME, '', { httpOnly: true, secure: true });
|
|
110
|
+
res.status(returnStatus).send(returnBody);
|
|
111
|
+
});
|
|
86
112
|
}
|
|
87
113
|
//# sourceMappingURL=web-server.js.map
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
7
|
-
"version": "0.10.0-alpha.
|
|
7
|
+
"version": "0.10.0-alpha.4",
|
|
8
8
|
"homepage": "https://developer.salesforce.com/docs/platform/lwr/overview",
|
|
9
9
|
"repository": {
|
|
10
10
|
"type": "git",
|
|
@@ -33,12 +33,12 @@
|
|
|
33
33
|
"node-fetch": "^2.6.8"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
|
-
"@lwrjs/server": "0.10.0-alpha.
|
|
37
|
-
"@lwrjs/types": "0.10.0-alpha.
|
|
36
|
+
"@lwrjs/server": "0.10.0-alpha.4",
|
|
37
|
+
"@lwrjs/types": "0.10.0-alpha.4",
|
|
38
38
|
"@types/node-fetch": "^2.5.12"
|
|
39
39
|
},
|
|
40
40
|
"engines": {
|
|
41
41
|
"node": ">=16.0.0 <20"
|
|
42
42
|
},
|
|
43
|
-
"gitHead": "
|
|
43
|
+
"gitHead": "f39a534d92fd3980b87839820f484729172e1067"
|
|
44
44
|
}
|