@lwrjs/auth-middleware 0.6.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/LICENSE ADDED
@@ -0,0 +1,10 @@
1
+ MIT LICENSE
2
+
3
+ Copyright (c) 2020, Salesforce.com, Inc.
4
+ All rights reserved.
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
7
+
8
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
9
+
10
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,138 @@
1
+ # LWR Authentication Middleware
2
+
3
+ This package contains middleware which authenticates to a [Salesforce Connected App](https://help.salesforce.com/s/articleView?id=sf.connected_app_overview.htm&type=5). Once authenticated, LWR app developers can make requests to their Salesforce org. The middleware works with both Express and Koa LWR server types.
4
+
5
+ - [Basic Example](#basic-example)
6
+ - [Connected App Setup](#connected-app-setup)
7
+ - [Details](#details)
8
+ - [Middleware](#middleware)
9
+ - [Variables](#variables)
10
+ - [Endpoints](#endpoints)
11
+ - [Flow](#flow)
12
+
13
+ ## Basic Example
14
+
15
+ Add the middleware to the LWR server:
16
+
17
+ ```ts
18
+ // excerpt from /my-lwr-app/src/index.ts
19
+ import { createServer } from 'lwr';
20
+ import { platformWebServerAuthMiddleware } from '@lwrjs/auth-middleware';
21
+
22
+ const lwrApp = createServer(lwrConfig);
23
+ platformWebServerAuthMiddleware(lwrApp.getServer()); // Pass in the generic LWR server interface
24
+ ```
25
+
26
+ > For more information on LWR middleware see [this recipe](https://github.com/salesforce/lwr-recipes/tree/master/packages/custom-middleware).
27
+
28
+ Start the LWR server, and pass the [Connected App information](#variables) in using environment variables:
29
+
30
+ ```
31
+ MY_DOMAIN=https://somewhere.force.com CLIENT_KEY=abc.123 CLIENT_SECRET=****** yarn start
32
+ ```
33
+
34
+ Authenticate by accessing the `/login` [endpoint](#endpoints) from the LWR app:
35
+
36
+ ```html
37
+ <!-- /my-lwr-app/src/modules/my/app/app.html -->
38
+ <a href="/login?app_path=%2Fhome">Login to Salesforce</a>
39
+ ```
40
+
41
+ Make authenticated requests from the LWR app through the [proxy endpoint](#endpoints):
42
+
43
+ ```js
44
+ // /my-lwr-app/src/modules/my/app/app.js
45
+
46
+ export default class MyApp extends LightningElement {
47
+ records;
48
+
49
+ getOpportunities() {
50
+ fetch('/services/data/v54.0/ui-api/list-ui/Opportunity/AllOpportunities')
51
+ .then((res) => {
52
+ res.json().then((data) => (this.records = data));
53
+ })
54
+ .catch((e) => {
55
+ console.error(e);
56
+ });
57
+ }
58
+ }
59
+ ```
60
+
61
+ ## Connected App Setup
62
+
63
+ Before using the LWR authentication middleware, the app developer must set up a [Connected App](https://help.salesforce.com/s/articleView?id=sf.connected_app_overview.htm&type=5):
64
+
65
+ 1. Follow [these instructions](https://help.salesforce.com/s/articleView?id=sf.connected_app_create_api_integration.htm&type=5) to set up the Connected App with OAuth enabled.
66
+ - Make selections for a "Web Server Flow" (not Device or JWT flows).
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
+ 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
+
80
+ ## Details
81
+
82
+ The `platformWebServerAuthMiddleware` uses the [OAuth 2.0 Web Server Flow for Web App Integration](https://help.salesforce.com/s/articleView?id=sf.remoteaccess_oauth_web_server_flow.htm&type=5).
83
+
84
+ ### Middleware
85
+
86
+ An app developer can attach the LWR authentication middleware when they create their LWR server. The middleware takes the following arguments:
87
+
88
+ - _Required_ app server instance from `getServer()`; this is compatible with both the Express and Koa LWR server types. Do not use `getInternalServer()`, which returns **either** the underlying Express **or** Koa server.
89
+ - _Optional_ bag of options: `{ proxyEndpoint?: string; }`:
90
+ - proxyEndpoint: The endpoint which proxies all its requests to the Salesforce org; it defaults to `/services/data`.
91
+
92
+ ```ts
93
+ import { createServer } from 'lwr';
94
+ import { platformWebServerAuthMiddleware } from '@lwrjs/auth-middleware';
95
+
96
+ const lwrApp = createServer(lwrConfig);
97
+ platformWebServerAuthMiddleware(lwrApp.getServer(), { proxyEndpoint: '/some/where' });
98
+ ```
99
+
100
+ ### Variables
101
+
102
+ In order to authenticate with the Web Server Flow, the following Connected App information is needed:
103
+
104
+ - `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).
105
+ - `CLIENT_KEY`: The public OAuth identifier for the Connected App (i.e. "Consumer Key", "Client ID").
106
+ - `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.
107
+
108
+ This information is passed to the LWR server via environment variables to keep them secure and out of the codebase.
109
+
110
+ > Read more about the OAuth Client ID and Secret [here](https://www.oauth.com/oauth2-servers/client-registration/client-id-secret/).
111
+
112
+ ### Endpoints
113
+
114
+ The LWR authentication middleware provides the following endpoints:
115
+
116
+ - `/login`: Accessed from the LWR app client code to trigger the OAuth flow and allow the current user to log in.
117
+ - 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.
118
+ - `/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.
119
+ - 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`.
120
+
121
+ ### Flow
122
+
123
+ The LWR server takes the following steps in order to authenticate and make authorized requests to a Salesforce org:
124
+
125
+ 1. The LWR authentication middleware receives a request to its `/login` endpoint.
126
+ 2. The middleware builds the `redirect_uri` by appending `/oauth` to the [origin](https://developer.mozilla.org/en-US/docs/Web/API/URL/origin).
127
+ 3. The middleware redirects to the org's `/services/oauth2/authorize` endpoint to request an authorization code.
128
+ 4. The user sees a login page in their browser and enters their username and password.
129
+ 5. The Connected app makes a request to the middleware's redirect URI (i.e. the `/oauth` endpoint) which includes the authorization code.
130
+ 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`).
132
+ 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
+
134
+ ```js
135
+ headers = {
136
+ Authorization: `Bearer ${token}`,
137
+ };
138
+ ```
@@ -0,0 +1,22 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getProtoOf = Object.getPrototypeOf;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
7
+ var __markAsModule = (target) => __defProp(target, "__esModule", {value: true});
8
+ var __exportStar = (target, module2, desc) => {
9
+ if (module2 && typeof module2 === "object" || typeof module2 === "function") {
10
+ for (let key of __getOwnPropNames(module2))
11
+ if (!__hasOwnProp.call(target, key) && key !== "default")
12
+ __defProp(target, key, {get: () => module2[key], enumerable: !(desc = __getOwnPropDesc(module2, key)) || desc.enumerable});
13
+ }
14
+ return target;
15
+ };
16
+ var __toModule = (module2) => {
17
+ return __exportStar(__markAsModule(__defProp(module2 != null ? __create(__getProtoOf(module2)) : {}, "default", module2 && module2.__esModule && "default" in module2 ? {get: () => module2.default, enumerable: true} : {value: module2, enumerable: true})), module2);
18
+ };
19
+
20
+ // packages/@lwrjs/auth-middleware/src/index.ts
21
+ __markAsModule(exports);
22
+ __exportStar(exports, __toModule(require("./web-server.cjs")));
@@ -0,0 +1,5 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __markAsModule = (target) => __defProp(target, "__esModule", {value: true});
3
+
4
+ // packages/@lwrjs/auth-middleware/src/types.ts
5
+ __markAsModule(exports);
@@ -0,0 +1,74 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __markAsModule = (target) => __defProp(target, "__esModule", {value: true});
3
+ var __export = (target, all) => {
4
+ for (var name in all)
5
+ __defProp(target, name, {get: all[name], enumerable: true});
6
+ };
7
+
8
+ // packages/@lwrjs/auth-middleware/src/utils.ts
9
+ __markAsModule(exports);
10
+ __export(exports, {
11
+ COOKIE_NAME: () => COOKIE_NAME,
12
+ LOGIN_ENDPOINT: () => LOGIN_ENDPOINT,
13
+ PROXY_ENDPOINT: () => PROXY_ENDPOINT,
14
+ REDIRECT_ENDPOINT: () => REDIRECT_ENDPOINT,
15
+ buildProxyRequest: () => buildProxyRequest,
16
+ buildRedirectUrl: () => buildRedirectUrl,
17
+ getAppPathAsState: () => getAppPathAsState,
18
+ getOauthInfoFromCookie: () => getOauthInfoFromCookie,
19
+ getPlatformWebServerEnvVars: () => getPlatformWebServerEnvVars
20
+ });
21
+ var LOGIN_ENDPOINT = "/login";
22
+ var REDIRECT_ENDPOINT = "/oauth";
23
+ var PROXY_ENDPOINT = "/services/data";
24
+ var COOKIE_NAME = "lwr_web_server_oauth_info";
25
+ var REDIRECT_QUERY = "app_path";
26
+ function buildRedirectUrl(req) {
27
+ return encodeURIComponent(`${req.protocol}://${req.get("host")}${REDIRECT_ENDPOINT}`);
28
+ }
29
+ function getPlatformWebServerEnvVars() {
30
+ const env = {myDomain: "MY_DOMAIN", clientKey: "CLIENT_KEY", clientSecret: "CLIENT_SECRET"};
31
+ const myDomain = process.env[env.myDomain];
32
+ const clientKey = process.env[env.clientKey];
33
+ const clientSecret = process.env[env.clientSecret];
34
+ const errorMsg = "Web Server OAuth Middleware: missing process.env.";
35
+ if (!myDomain) {
36
+ throw new Error(`${errorMsg}${env.myDomain}`);
37
+ }
38
+ if (!clientKey) {
39
+ throw new Error(`${errorMsg}${env.clientKey}`);
40
+ }
41
+ if (!clientSecret) {
42
+ throw new Error(`${errorMsg}${env.clientSecret}`);
43
+ }
44
+ return {myDomain, clientKey, clientSecret};
45
+ }
46
+ function getAppPathAsState(req) {
47
+ let appPath = req.query[REDIRECT_QUERY];
48
+ if (appPath && !decodeURIComponent(appPath).startsWith("/")) {
49
+ console.warn('The "app_path" parameter must be a relative path staring with "/". Defaulting to "app_path" = "/".');
50
+ appPath = void 0;
51
+ }
52
+ return appPath ? `&state=${appPath}` : "";
53
+ }
54
+ function getOauthInfoFromCookie(req) {
55
+ const oauthInfoStr = req.cookie(COOKIE_NAME);
56
+ return oauthInfoStr ? JSON.parse(oauthInfoStr) : void 0;
57
+ }
58
+ function buildProxyRequest(req, {access_token, instance_url}) {
59
+ const headers = {
60
+ Authorization: `Bearer ${access_token}`
61
+ };
62
+ let body;
63
+ if (req.method !== "GET" && req.body) {
64
+ body = JSON.stringify(req.body);
65
+ }
66
+ return {
67
+ url: `${instance_url}${req.originalUrl}`,
68
+ options: {
69
+ method: req.method,
70
+ headers,
71
+ body
72
+ }
73
+ };
74
+ }
@@ -0,0 +1,74 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getProtoOf = Object.getPrototypeOf;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
7
+ var __markAsModule = (target) => __defProp(target, "__esModule", {value: true});
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, {get: all[name], enumerable: true});
11
+ };
12
+ var __exportStar = (target, module2, desc) => {
13
+ if (module2 && typeof module2 === "object" || typeof module2 === "function") {
14
+ for (let key of __getOwnPropNames(module2))
15
+ if (!__hasOwnProp.call(target, key) && key !== "default")
16
+ __defProp(target, key, {get: () => module2[key], enumerable: !(desc = __getOwnPropDesc(module2, key)) || desc.enumerable});
17
+ }
18
+ return target;
19
+ };
20
+ var __toModule = (module2) => {
21
+ return __exportStar(__markAsModule(__defProp(module2 != null ? __create(__getProtoOf(module2)) : {}, "default", module2 && module2.__esModule && "default" in module2 ? {get: () => module2.default, enumerable: true} : {value: module2, enumerable: true})), module2);
22
+ };
23
+
24
+ // packages/@lwrjs/auth-middleware/src/web-server.ts
25
+ __markAsModule(exports);
26
+ __export(exports, {
27
+ platformWebServerAuthMiddleware: () => platformWebServerAuthMiddleware
28
+ });
29
+ var import_node_fetch = __toModule(require("node-fetch"));
30
+ var import_utils = __toModule(require("./utils.cjs"));
31
+ var MESSAGE_PREFIX = "Web Server OAuth Middleware - ";
32
+ function platformWebServerAuthMiddleware(server, {proxyEndpoint = import_utils.PROXY_ENDPOINT} = {}) {
33
+ let redirectUri;
34
+ const {myDomain, clientKey, clientSecret} = (0, import_utils.getPlatformWebServerEnvVars)();
35
+ server.get(import_utils.LOGIN_ENDPOINT, (req, res) => {
36
+ redirectUri = redirectUri || (0, import_utils.buildRedirectUrl)(req);
37
+ const state = (0, import_utils.getAppPathAsState)(req);
38
+ const loginUri = `${myDomain}/services/oauth2/authorize?response_type=code`;
39
+ res.set({Location: `${loginUri}&client_id=${clientKey}&redirect_uri=${redirectUri}${state}`});
40
+ res.sendStatus(302);
41
+ });
42
+ server.get(import_utils.REDIRECT_ENDPOINT, async (req, res) => {
43
+ const {code, state} = req.query;
44
+ const appPath = state ? decodeURIComponent(state) : "/";
45
+ const response = await (0, import_node_fetch.default)(`${myDomain}/services/oauth2/token`, {
46
+ method: "POST",
47
+ headers: {"Content-Type": "application/x-www-form-urlencoded", Accept: "application/json"},
48
+ body: `grant_type=authorization_code&code=${code}&client_id=${clientKey}&client_secret=${clientSecret}&redirect_uri=${redirectUri}`
49
+ });
50
+ const data = await response.json();
51
+ if (response.ok) {
52
+ const {access_token, instance_url} = data;
53
+ res.cookie(import_utils.COOKIE_NAME, JSON.stringify({access_token, instance_url}), {httpOnly: true});
54
+ res.set({Location: appPath});
55
+ res.sendStatus(302);
56
+ } else {
57
+ const errorMsg = `${MESSAGE_PREFIX}could not authenticate.`;
58
+ console.error(errorMsg, data);
59
+ res.status(response.status).send(errorMsg);
60
+ }
61
+ });
62
+ server.all(`${proxyEndpoint}/${server.getRegexWildcard()}`, async (req, res) => {
63
+ const oauthInfo = (0, import_utils.getOauthInfoFromCookie)(req);
64
+ if (oauthInfo) {
65
+ const {url, options} = (0, import_utils.buildProxyRequest)(req, oauthInfo);
66
+ const proxyRes = await (0, import_node_fetch.default)(url, options);
67
+ const contentType = proxyRes.headers.get("Content-Type") || "";
68
+ const data = await (contentType.includes("application/json") ? proxyRes.json() : proxyRes.text());
69
+ res.status(proxyRes.status).send(data);
70
+ } else {
71
+ res.status(401).send(`${MESSAGE_PREFIX}user is not authenticated.`);
72
+ }
73
+ });
74
+ }
@@ -0,0 +1,2 @@
1
+ export * from './web-server.js';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,2 @@
1
+ export * from './web-server.js';
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,27 @@
1
+ export interface PlatformWebServerInput {
2
+ /** base URL to the Connected App, e.g. "https://lwr.lightning.force.com" (no trailing slash) */
3
+ myDomain: string;
4
+ /** "Consumer Key" from the Connected App */
5
+ clientKey: string;
6
+ /** "Consumer Secret" from the Connected App */
7
+ clientSecret: string;
8
+ }
9
+ export interface PlatformWebServerOptions {
10
+ /** The endpoint path through which all authenticated/proxied requests pass */
11
+ proxyEndpoint?: string;
12
+ }
13
+ export interface OAuthInfo {
14
+ /** The OAuth token, used to authenticate requests via the "Authentication: Bearer <token>" header */
15
+ access_token: string;
16
+ /** The URL to the Connected App which receives authenticated requests */
17
+ instance_url: string;
18
+ }
19
+ export interface RequestOptions {
20
+ url: string;
21
+ options: {
22
+ method: string;
23
+ headers?: Record<string, string>;
24
+ body?: string;
25
+ };
26
+ }
27
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1,12 @@
1
+ import type { MiddlewareRequest } from '@lwrjs/types';
2
+ import type { OAuthInfo, RequestOptions, PlatformWebServerInput } from './types.js';
3
+ export declare const LOGIN_ENDPOINT = "/login";
4
+ export declare const REDIRECT_ENDPOINT = "/oauth";
5
+ export declare const PROXY_ENDPOINT = "/services/data";
6
+ export declare const COOKIE_NAME = "lwr_web_server_oauth_info";
7
+ export declare function buildRedirectUrl(req: MiddlewareRequest): string;
8
+ export declare function getPlatformWebServerEnvVars(): PlatformWebServerInput;
9
+ export declare function getAppPathAsState(req: MiddlewareRequest): string;
10
+ export declare function getOauthInfoFromCookie(req: MiddlewareRequest): OAuthInfo | undefined;
11
+ export declare function buildProxyRequest(req: MiddlewareRequest, { access_token, instance_url }: OAuthInfo): RequestOptions;
12
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1,64 @@
1
+ export const LOGIN_ENDPOINT = '/login';
2
+ export const REDIRECT_ENDPOINT = '/oauth';
3
+ export const PROXY_ENDPOINT = '/services/data';
4
+ export const COOKIE_NAME = 'lwr_web_server_oauth_info';
5
+ const REDIRECT_QUERY = 'app_path';
6
+ // Create an OAuth redirect URI from the origin of a request
7
+ // Return the redirect URI encoded so it can be used as a query param
8
+ export function buildRedirectUrl(req) {
9
+ return encodeURIComponent(`${req.protocol}://${req.get('host')}${REDIRECT_ENDPOINT}`);
10
+ }
11
+ // Retrieve and validate environment variables for the Web Server OAuth flow
12
+ export function getPlatformWebServerEnvVars() {
13
+ const env = { myDomain: 'MY_DOMAIN', clientKey: 'CLIENT_KEY', clientSecret: 'CLIENT_SECRET' };
14
+ const myDomain = process.env[env.myDomain];
15
+ const clientKey = process.env[env.clientKey];
16
+ const clientSecret = process.env[env.clientSecret];
17
+ // The inputs are all required
18
+ const errorMsg = 'Web Server OAuth Middleware: missing process.env.';
19
+ if (!myDomain) {
20
+ throw new Error(`${errorMsg}${env.myDomain}`);
21
+ }
22
+ if (!clientKey) {
23
+ throw new Error(`${errorMsg}${env.clientKey}`);
24
+ }
25
+ if (!clientSecret) {
26
+ throw new Error(`${errorMsg}${env.clientSecret}`);
27
+ }
28
+ return { myDomain, clientKey, clientSecret };
29
+ }
30
+ // Parse the app_path query param, validate it, and return it as a state query param
31
+ export function getAppPathAsState(req) {
32
+ let appPath = req.query[REDIRECT_QUERY];
33
+ if (appPath && !decodeURIComponent(appPath).startsWith('/')) {
34
+ console.warn('The "app_path" parameter must be a relative path staring with "/". Defaulting to "app_path" = "/".');
35
+ appPath = undefined;
36
+ }
37
+ return appPath ? `&state=${appPath}` : '';
38
+ }
39
+ // Get the token and url from the cookie
40
+ export function getOauthInfoFromCookie(req) {
41
+ const oauthInfoStr = req.cookie(COOKIE_NAME);
42
+ return oauthInfoStr ? JSON.parse(oauthInfoStr) : undefined;
43
+ }
44
+ // Build an authenticated request based on a request to the proxy endpoint
45
+ export function buildProxyRequest(req, { access_token, instance_url }) {
46
+ // Add the oauth header, future: forward any headers from the client?
47
+ const headers = {
48
+ Authorization: `Bearer ${access_token}`, // add the oauth header
49
+ };
50
+ // Pull the body from non-GET requests
51
+ let body;
52
+ if (req.method !== 'GET' && req.body) {
53
+ body = JSON.stringify(req.body);
54
+ }
55
+ return {
56
+ url: `${instance_url}${req.originalUrl}`,
57
+ options: {
58
+ method: req.method,
59
+ headers: headers,
60
+ body,
61
+ },
62
+ };
63
+ }
64
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1,13 @@
1
+ import type { InternalAppServer, ServerTypes } from '@lwrjs/types';
2
+ import type { PlatformWebServerOptions } from './types.js';
3
+ /**
4
+ * Web Server OAuth middleware: https://help.salesforce.com/s/articleView?id=sf.remoteaccess_oauth_web_server_flow.htm&type=5
5
+ * Accepts the following environment variables:
6
+ * - process.env.MY_DOMAIN
7
+ * - process.env.CLIENT_KEY
8
+ * - process.env.CLIENT_SECRET
9
+ * The login endpoint accepts the following query parameters:
10
+ * - app_path: a path encoded string; the middleware redirects to this path after setting the OAuth token in a cookie; default is "/"
11
+ */
12
+ export declare function platformWebServerAuthMiddleware<T extends ServerTypes>(server: InternalAppServer<T>, { proxyEndpoint }?: PlatformWebServerOptions): void;
13
+ //# sourceMappingURL=web-server.d.ts.map
@@ -0,0 +1,71 @@
1
+ import fetch from 'node-fetch';
2
+ import { COOKIE_NAME, LOGIN_ENDPOINT, PROXY_ENDPOINT, REDIRECT_ENDPOINT, buildProxyRequest, buildRedirectUrl, getAppPathAsState, getOauthInfoFromCookie, getPlatformWebServerEnvVars, } from './utils.js';
3
+ const MESSAGE_PREFIX = 'Web Server OAuth Middleware - ';
4
+ /**
5
+ * Web Server OAuth middleware: https://help.salesforce.com/s/articleView?id=sf.remoteaccess_oauth_web_server_flow.htm&type=5
6
+ * Accepts the following environment variables:
7
+ * - process.env.MY_DOMAIN
8
+ * - process.env.CLIENT_KEY
9
+ * - process.env.CLIENT_SECRET
10
+ * The login endpoint accepts the following query parameters:
11
+ * - app_path: a path encoded string; the middleware redirects to this path after setting the OAuth token in a cookie; default is "/"
12
+ */
13
+ export function platformWebServerAuthMiddleware(server, { proxyEndpoint = PROXY_ENDPOINT } = {}) {
14
+ let redirectUri;
15
+ const { myDomain, clientKey, clientSecret } = getPlatformWebServerEnvVars();
16
+ // First, request an authorization code by redirecting to Salesforce login
17
+ // The LWR app should call this endpoint when authentication is needed
18
+ server.get(LOGIN_ENDPOINT, (req, res) => {
19
+ // Build the redirect URI and save the app path in the OAuth state, if included
20
+ redirectUri = redirectUri || buildRedirectUrl(req);
21
+ const state = getAppPathAsState(req);
22
+ // Redirect to the Salesforce login page
23
+ const loginUri = `${myDomain}/services/oauth2/authorize?response_type=code`;
24
+ res.set({ Location: `${loginUri}&client_id=${clientKey}&redirect_uri=${redirectUri}${state}` });
25
+ res.sendStatus(302);
26
+ });
27
+ // Second, use the authorization code from the redirect URI to retrieve the token
28
+ // This route MUST BE the "Callback URL" in the Connected App OAuth settings
29
+ server.get(REDIRECT_ENDPOINT, async (req, res) => {
30
+ // Parse the code and state (i.e. app_path) query params
31
+ const { code, state } = req.query;
32
+ const appPath = state ? decodeURIComponent(state) : '/';
33
+ // Fetch the OAuth token
34
+ const response = await fetch(`${myDomain}/services/oauth2/token`, {
35
+ method: 'POST',
36
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded', Accept: 'application/json' },
37
+ body: `grant_type=authorization_code&code=${code}&client_id=${clientKey}&client_secret=${clientSecret}&redirect_uri=${redirectUri}`,
38
+ });
39
+ // Response handling
40
+ const data = await response.json();
41
+ if (response.ok) {
42
+ // Store the OAuth info in a cookie and redirect to the app
43
+ const { access_token, instance_url } = data;
44
+ res.cookie(COOKIE_NAME, JSON.stringify({ access_token, instance_url }), { httpOnly: true });
45
+ res.set({ Location: appPath });
46
+ res.sendStatus(302);
47
+ }
48
+ else {
49
+ // Print and forward the error JSON to the client
50
+ const errorMsg = `${MESSAGE_PREFIX}could not authenticate.`;
51
+ console.error(errorMsg, data);
52
+ res.status(response.status).send(errorMsg);
53
+ }
54
+ });
55
+ // Now, proxy requests using the token to authorize them
56
+ // e.g. /services/data/v52.0/ui-api/list-ui/Opportunity/AllOpportunities"
57
+ server.all(`${proxyEndpoint}/${server.getRegexWildcard()}`, async (req, res) => {
58
+ const oauthInfo = getOauthInfoFromCookie(req);
59
+ if (oauthInfo) {
60
+ const { url, options } = buildProxyRequest(req, oauthInfo);
61
+ const proxyRes = await fetch(url, options);
62
+ const contentType = proxyRes.headers.get('Content-Type') || ''; // parse as json or text
63
+ const data = await (contentType.includes('application/json') ? proxyRes.json() : proxyRes.text());
64
+ res.status(proxyRes.status).send(data);
65
+ }
66
+ else {
67
+ res.status(401).send(`${MESSAGE_PREFIX}user is not authenticated.`);
68
+ }
69
+ });
70
+ }
71
+ //# sourceMappingURL=web-server.js.map
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@lwrjs/auth-middleware",
3
+ "license": "MIT",
4
+ "publishConfig": {
5
+ "access": "public"
6
+ },
7
+ "version": "0.6.0",
8
+ "homepage": "https://developer.salesforce.com/docs/platform/lwr/overview",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "https://github.com/salesforce/lwr.git",
12
+ "directory": "packages/@lwrjs/auth-middleware"
13
+ },
14
+ "bugs": {
15
+ "url": "https://github.com/salesforce/lwr/issues"
16
+ },
17
+ "type": "module",
18
+ "types": "build/es/index.d.ts",
19
+ "main": "build/cjs/index.cjs",
20
+ "module": "build/es/index.js",
21
+ "exports": {
22
+ ".": {
23
+ "import": "./build/es/index.js",
24
+ "require": "./build/cjs/index.cjs"
25
+ }
26
+ },
27
+ "files": [
28
+ "build/**/*.js",
29
+ "build/**/*.cjs",
30
+ "build/**/*.d.ts"
31
+ ],
32
+ "devDependencies": {
33
+ "@lwrjs/server": "0.6.0",
34
+ "@lwrjs/types": "0.6.0",
35
+ "@types/node-fetch": "^2.5.12",
36
+ "node-fetch": "^2.6.1"
37
+ },
38
+ "engines": {
39
+ "node": ">=14.15.4 <17"
40
+ },
41
+ "gitHead": "31769655f0155ad7e54cf37bccdf72d0baaf44ab"
42
+ }