@grasp-labs/ds-microfrontends-integration 0.6.2 → 0.8.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/README.md CHANGED
@@ -218,6 +218,21 @@ These dependencies are configured as singletons to prevent multiple instances an
218
218
  npm install
219
219
  ```
220
220
 
221
+ ### Dev auth middleware
222
+
223
+ `devAuthMiddleware` adds a quick login screen to your local Express setup and reuses the returned token for every backend call. Mount it before anything that proxies to your APIs so outbound requests get `Authorization: Bearer <token>` automatically.
224
+
225
+ ```ts
226
+ import express from "express";
227
+ import { devAuthMiddleware } from "@grasp-labs/ds-microfrontends-integration/dev";
228
+
229
+ const app = express();
230
+
231
+ app.use(devAuthMiddleware());
232
+ ```
233
+
234
+ Skip the form entirely by setting `DEV_AUTH_TOKEN` (env var or `.env` file) before starting the server. Need a token? Call the dev auth endpoint with your usual credentials and copy the `access_token`.
235
+
221
236
  ### Scripts
222
237
 
223
238
  - `npm run build` - Build the library
@@ -0,0 +1,49 @@
1
+ import { Request, Response, NextFunction } from 'express';
2
+ export type DevLoginOptions = {
3
+ /**
4
+ * Path segment or absolute path for the login page. Values are trimmed and
5
+ * automatically prefixed with "/" when missing (e.g. "__login" => "/__login").
6
+ * @default "/__login"
7
+ */
8
+ loginPath?: string;
9
+ /**
10
+ * Location to redirect to after successful authentication. The same
11
+ * normalization rules apply as for {@link loginPath}.
12
+ * @default "/"
13
+ */
14
+ redirectAfterLoginPathTo?: string;
15
+ /**
16
+ * Fully qualified URL of the backend dev auth login endpoint.
17
+ * @default "https://auth-dev.grasp-daas.com/rest-auth/login/"
18
+ */
19
+ authEndpoint?: string;
20
+ };
21
+ /**
22
+ * Simple Express middleware that forces a one-page dev login and then injects
23
+ * `Authorization: Bearer <token>` for subsequent requests. Mount it ahead of
24
+ * any backend proxy (e.g. `createProxyMiddleware`) so outgoing calls receive
25
+ * the token.
26
+ *
27
+ * Skip the login screen by setting `DEV_AUTH_TOKEN` before starting your app
28
+ * (via `.env` or the shell). Tokens can be created by calling the dev auth
29
+ * endpoint with your normal credentials and copying the `access_token`.
30
+ *
31
+ * @example
32
+ * ```typescript
33
+ * import express from 'express';
34
+ * import { devAuthMiddleware } from '@grasp-labs/ds-microfrontends-integration/dev';
35
+ *
36
+ * const app = express();
37
+ * app.use(devAuthMiddleware());
38
+ * ```
39
+ */
40
+ export declare function devAuthMiddleware(options?: DevLoginOptions): (req: Request, res: Response, next: NextFunction) => Promise<any>;
41
+ /**
42
+ * Simple in-memory token store backed by process.env.
43
+ * Preloading `DEV_AUTH_TOKEN` (e.g. via a `.env` file or shell env var) skips the login flow entirely.
44
+ */
45
+ export declare const DEV_AUTH_TOKEN: {
46
+ readonly get: () => string | undefined;
47
+ readonly set: (token: string) => void;
48
+ readonly exists: () => boolean;
49
+ };
@@ -0,0 +1,242 @@
1
+ import { urlencoded as b } from "express";
2
+ function w(e) {
3
+ const t = e.loginPath, r = e.errorMessage ?? "", n = y(r);
4
+ return `<!DOCTYPE html>
5
+ <html lang="en">
6
+ <head>
7
+ <meta charset="UTF-8" />
8
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
9
+ <title>Login</title>
10
+ <style>
11
+ :root {
12
+ --border: 1px solid oklch(100% 0 0/0.1);
13
+ }
14
+
15
+ * {
16
+ margin: 0;
17
+ padding: 0;
18
+ box-sizing: border-box;
19
+ font-size: 14px;
20
+ border-radius: 8px;
21
+ }
22
+
23
+ body {
24
+ font-family: sans-serif;
25
+ background-color: oklch(0.14 0 0);
26
+ color: oklch(1 0 0);
27
+ display: flex;
28
+ justify-content: center;
29
+ padding-top: 20svh;
30
+ }
31
+
32
+ main {
33
+ width: 100%;
34
+ max-width: 320px;
35
+ }
36
+
37
+ h1 {
38
+ font-size: 24px;
39
+ margin-bottom: 8px;
40
+ color: oklch(0.95 0 0);
41
+ }
42
+
43
+ p {
44
+ color: oklch(0.6 0 0);
45
+ margin-bottom: 24px;
46
+ }
47
+
48
+ form {
49
+ display: flex;
50
+ flex-direction: column;
51
+ gap: 14px;
52
+ }
53
+
54
+ input {
55
+ padding: 10px 12px;
56
+ border: var(--border);
57
+ background-color: oklch(0.2 0 0);
58
+ color: oklch(0.95 0 0);
59
+ }
60
+
61
+ input:focus {
62
+ outline: none;
63
+ border-color: oklch(0.7 0 0);
64
+ }
65
+
66
+ button {
67
+ padding: 10px 12px;
68
+ border: none;
69
+ background-color: oklch(0.488 0.243 264.376);
70
+ color: white;
71
+ cursor: pointer;
72
+ }
73
+
74
+ button:hover {
75
+ background-color: oklch(0.588 0.243 264.376);
76
+ }
77
+
78
+ .error-block {
79
+ padding: 12px;
80
+ margin-top: 20px;
81
+ border: var(--border);
82
+ background-color: oklch(0.20 0 0);
83
+ color: oklch(0.71 0.17 22);
84
+ margin-bottom: 14px;
85
+ display: none;
86
+ }
87
+
88
+ .error-block pre {
89
+ margin: 0;
90
+ white-space: pre-wrap;
91
+ word-break: break-word;
92
+ }
93
+ </style>
94
+ </head>
95
+ <body>
96
+ <main>
97
+ <div>
98
+ <h1>Login</h1>
99
+ <p>Provide credentials to unlock the dev server</p>
100
+ </div>
101
+
102
+ <form method="POST" action="${t}">
103
+ <input type="text" placeholder="Email" name="email" required />
104
+ <input
105
+ type="password"
106
+ placeholder="Password"
107
+ name="password"
108
+ required
109
+ />
110
+ <button type="submit">Login</button>
111
+ <div class="error-block" style="display: ${r ? "block" : "none"};">
112
+ <pre>${n}</pre>
113
+ </div>
114
+ </form>
115
+ </main>
116
+ </body>
117
+ </html>`;
118
+ }
119
+ function y(e) {
120
+ return e.replace(/[&<>"']/g, (t) => {
121
+ switch (t) {
122
+ case "&":
123
+ return "&amp;";
124
+ case "<":
125
+ return "&lt;";
126
+ case ">":
127
+ return "&gt;";
128
+ case '"':
129
+ return "&quot;";
130
+ case "'":
131
+ return "&#39;";
132
+ default:
133
+ return t;
134
+ }
135
+ });
136
+ }
137
+ function E(e = {}) {
138
+ const {
139
+ loginPath: t = "/__login",
140
+ redirectAfterLoginPathTo: r = "/",
141
+ authEndpoint: n = "https://auth-dev.grasp-daas.com/rest-auth/login/"
142
+ } = e, o = g(t, "loginPath"), c = g(
143
+ r,
144
+ "redirectAfterLoginPathTo"
145
+ ), m = k();
146
+ return async (s, i, p) => {
147
+ if (l.exists())
148
+ return s.path === o ? i.redirect(c) : (s.headers.authorization = `Bearer ${l.get()}`, p());
149
+ if (s.path !== o)
150
+ return i.redirect(o);
151
+ if (s.method === "GET")
152
+ return d(i, o);
153
+ if (s.method === "POST") {
154
+ try {
155
+ await m(s, i);
156
+ } catch (a) {
157
+ return p(a);
158
+ }
159
+ const { email: u, password: h } = s.body ?? {};
160
+ if (!u || !h)
161
+ return d(i, o, "Email and password are required.");
162
+ try {
163
+ const { access_token: a } = await x({
164
+ email: u,
165
+ password: h,
166
+ authEndpoint: n
167
+ });
168
+ return l.set(a), i.redirect(303, c);
169
+ } catch (a) {
170
+ const f = `${a instanceof Error ? a.message : String(a)}`;
171
+ return d(i, o, f);
172
+ }
173
+ }
174
+ return i.status(405).send("Method Not Allowed");
175
+ };
176
+ }
177
+ function g(e, t) {
178
+ const r = e.trim();
179
+ if (!r)
180
+ throw new Error(
181
+ `devAuthMiddleware ${t} must be a non-empty string`
182
+ );
183
+ return r.startsWith("/") ? r : `/${r}`;
184
+ }
185
+ function d(e, t, r) {
186
+ return e.type("html").send(
187
+ w({
188
+ loginPath: t,
189
+ errorMessage: r
190
+ })
191
+ );
192
+ }
193
+ function k() {
194
+ const e = b({ extended: !0 });
195
+ return (t, r) => new Promise((n, o) => {
196
+ e(t, r, (c) => {
197
+ if (c) {
198
+ o(c);
199
+ return;
200
+ }
201
+ n();
202
+ });
203
+ });
204
+ }
205
+ async function x({
206
+ email: e,
207
+ password: t,
208
+ authEndpoint: r
209
+ }) {
210
+ const n = await fetch(r, {
211
+ method: "POST",
212
+ headers: {
213
+ "Content-Type": "application/json"
214
+ },
215
+ body: JSON.stringify({
216
+ email: e,
217
+ password: t,
218
+ remember_me: !0
219
+ })
220
+ });
221
+ if (!n.ok)
222
+ throw new Error(
223
+ `Authentication failed with status ${n.status} and response: ${await n.text()}`
224
+ );
225
+ const o = await n.json();
226
+ if (!("access_token" in o && "refresh_token" in o))
227
+ throw new Error(
228
+ `Authentication response is missing access_token or refresh_token: ${JSON.stringify(o, null, 2)}`
229
+ );
230
+ return o;
231
+ }
232
+ const l = {
233
+ get: () => process.env.DEV_AUTH_TOKEN,
234
+ set: (e) => {
235
+ process.env.DEV_AUTH_TOKEN = e;
236
+ },
237
+ exists: () => typeof process.env.DEV_AUTH_TOKEN == "string" && process.env.DEV_AUTH_TOKEN.length > 0
238
+ };
239
+ export {
240
+ l as DEV_AUTH_TOKEN,
241
+ E as devAuthMiddleware
242
+ };
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Renders the login page markup with optional error messaging.
3
+ */
4
+ export declare function buildLoginPage(options: {
5
+ loginPath: string;
6
+ errorMessage?: string;
7
+ }): string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@grasp-labs/ds-microfrontends-integration",
3
- "version": "0.6.2",
3
+ "version": "0.8.0",
4
4
  "private": false,
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -17,6 +17,10 @@
17
17
  "import": "./dist/mf-common.js",
18
18
  "types": "./dist/mf-common.d.ts"
19
19
  },
20
+ "./dev": {
21
+ "import": "./dist/dev/express-auth-middleware.js",
22
+ "types": "./dist/dev/express-auth-middleware.d.ts"
23
+ },
20
24
  "./styles.css": "./src/index.css"
21
25
  },
22
26
  "files": [
@@ -44,11 +48,17 @@
44
48
  "@module-federation/vite": "^1.0.0",
45
49
  "ajv": "^8.0.0",
46
50
  "axios": "^1.7.9",
51
+ "express": "^5.0.0",
47
52
  "react": "^19.1.0",
48
53
  "react-dom": "^19.1.0",
49
54
  "react-hook-form": "^7.0.0",
50
55
  "react-router": "^7.8.2"
51
56
  },
57
+ "peerDependenciesMeta": {
58
+ "express": {
59
+ "optional": true
60
+ }
61
+ },
52
62
  "devDependencies": {
53
63
  "@eslint/js": "^9.32.0",
54
64
  "@grasp-labs/ds-react-components": "^0.13.0",
@@ -60,6 +70,7 @@
60
70
  "@testing-library/jest-dom": "^6.6.3",
61
71
  "@testing-library/react": "^16.3.0",
62
72
  "@testing-library/user-event": "^14.6.1",
73
+ "@types/express": "^5.0.5",
63
74
  "@types/node": "^22.16.5",
64
75
  "@types/react": "^19.1.10",
65
76
  "@types/react-dom": "^19.1.7",
@@ -71,6 +82,7 @@
71
82
  "eslint-plugin-prettier": "^5.5.3",
72
83
  "eslint-plugin-react-hooks": "^5.2.0",
73
84
  "eslint-plugin-storybook": "^9.1.0",
85
+ "express": "^5.1.0",
74
86
  "globals": "^16.2.0",
75
87
  "jsdom": "^26.1.0",
76
88
  "json-edit-react": "^1.29.0",