@econneq/gql-auth 1.2.10 → 2.0.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.
Files changed (51) hide show
  1. package/README.md +304 -22
  2. package/dist/api-factory.d.ts +11 -29
  3. package/dist/api-factory.d.ts.map +1 -1
  4. package/dist/api-factory.js +98 -47
  5. package/dist/api-factory.js.map +1 -0
  6. package/dist/authServer.d.ts +32 -0
  7. package/dist/authServer.d.ts.map +1 -0
  8. package/dist/authServer.js +93 -0
  9. package/dist/authServer.js.map +1 -0
  10. package/dist/client.d.ts +28 -14
  11. package/dist/client.d.ts.map +1 -1
  12. package/dist/client.js +77 -36
  13. package/dist/client.js.map +1 -0
  14. package/dist/env.d.ts +35 -0
  15. package/dist/env.d.ts.map +1 -0
  16. package/dist/env.js +65 -0
  17. package/dist/env.js.map +1 -0
  18. package/dist/errors.d.ts +10 -0
  19. package/dist/errors.d.ts.map +1 -0
  20. package/dist/errors.js +24 -0
  21. package/dist/errors.js.map +1 -0
  22. package/dist/functions.d.ts +24 -0
  23. package/dist/functions.d.ts.map +1 -0
  24. package/dist/functions.js +86 -0
  25. package/dist/functions.js.map +1 -0
  26. package/dist/index.d.ts +11 -84
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +22 -8
  29. package/dist/index.js.map +1 -0
  30. package/dist/query-server.d.ts +31 -12
  31. package/dist/query-server.d.ts.map +1 -1
  32. package/dist/query-server.js +63 -20
  33. package/dist/query-server.js.map +1 -0
  34. package/dist/tenant.d.ts +21 -0
  35. package/dist/tenant.d.ts.map +1 -0
  36. package/dist/tenant.js +50 -0
  37. package/dist/tenant.js.map +1 -0
  38. package/dist/types.d.ts +124 -0
  39. package/dist/types.d.ts.map +1 -0
  40. package/dist/types.js +5 -0
  41. package/dist/types.js.map +1 -0
  42. package/dist/upload-gql.d.ts +2 -20
  43. package/dist/upload-gql.d.ts.map +1 -1
  44. package/dist/upload-gql.js +63 -33
  45. package/dist/upload-gql.js.map +1 -0
  46. package/dist/utils.d.ts +24 -0
  47. package/dist/utils.d.ts.map +1 -0
  48. package/dist/utils.js +257 -0
  49. package/dist/utilsGeneral.d.ts +1 -1
  50. package/dist/utilsGeneral.d.ts.map +1 -1
  51. package/package.json +49 -22
package/README.md CHANGED
@@ -1,22 +1,304 @@
1
- # @econneq/gql-auth
2
- ![NPM Version](https://img.shields.io/npm/v/@econneq/gql-auth)
3
-
4
- A high-performance, multi-tenant GraphQL abstraction layer designed for **Next.js (App Router)** and **Apollo Client**.
5
-
6
- This package standardizes how frontend projects interact with a Django GraphQL backend, handling tenant detection, SSR cookie forwarding, and multipart file uploads.
7
-
8
- ---
9
-
10
- ## Features
11
-
12
- * 🌍 **Automatic Tenant Injection**: Detects subdomains and injects `X-Tenant-ID`, `X-Process-ID`, and `X-Widget-ID` headers.
13
- * 🔐 **SSR Ready**: Built-in support for `next/headers` to forward authentication cookies during Server-Side Rendering.
14
- * 📁 **Smart File Uploads**: Simplifies `multipart/form-data` logic for GraphQL mutations.
15
- * 🚀 **Lightweight**: Optimized to use peer dependencies, keeping your bundle size minimal.
16
-
17
- ---
18
-
19
- ## 📦 Installation
20
-
21
- ```bash
22
- npm install @econneq/gql-auth
1
+ # @econneq/gql-auth
2
+
3
+ GraphQL + Auth utilities for **multi-tenant** and **single-tenant** Next.js apps.
4
+
5
+ Handles:
6
+ - Server-side (internal Docker network) and client-side (browser proxy) GraphQL
7
+ - Multi-tenant header injection (`X-Tenant-ID`, `X-Process-ID`, `X-Widget-ID`)
8
+ - JWT auth on both server and client
9
+ - File uploads (GraphQL multipart spec)
10
+ - Batch mutations with per-item error reporting
11
+
12
+ ---
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ npm install @econneq/gql-auth
18
+ ```
19
+
20
+ ### Peer dependencies
21
+
22
+ ```bash
23
+ npm install @apollo/client graphql next react
24
+ ```
25
+
26
+ ---
27
+
28
+ ## Environment Variables
29
+
30
+ | Variable | Side | Description |
31
+ |---|---|---|
32
+ | `DOMAIN_URL` | server | Base domain, e.g. `e-conneq.com` |
33
+ | `PROTOCOL` | server | e.g. `https://` |
34
+ | `BACKEND_URL_INT` | server | Internal Docker URL, e.g. `api:8000` |
35
+ | `BACKEND_URL_EXT` | server | External backend host, e.g. `admin.e-conneq.com` |
36
+ | `NEXT_PUBLIC_DOMAIN_URL` | both | Same as DOMAIN_URL (client-accessible) |
37
+ | `NEXT_PUBLIC_PROTOCOL` | both | Same as PROTOCOL (client-accessible) |
38
+ | `NEXT_PUBLIC_BACKEND_URL_EXT` | both | Same as BACKEND_URL_EXT (client-accessible) |
39
+ | `API_KEY` | server | Optional service-to-service key |
40
+
41
+ > The internal URL (`BACKEND_URL_INT`) is **never** exposed to the browser.
42
+ > Client-side mutations route through Next.js `/api/graphql` proxy.
43
+
44
+ ---
45
+
46
+ ## Config Object
47
+
48
+ Every function accepts a `config` object describing your tenants.
49
+
50
+ ```ts
51
+ // config.ts (shared across your project)
52
+ import type { ConfigType } from "@econneq/gql-auth";
53
+
54
+ export const config: ConfigType = {
55
+ Subdomains: [
56
+ { id: 1, subdomain: "acme", processId: "proc-1", widgetID: "wgt-1" },
57
+ { id: 2, subdomain: "globex", processId: "proc-2" },
58
+ ],
59
+ };
60
+ ```
61
+
62
+ **Single-tenant**: put one entry in `Subdomains`. The library will always use it
63
+ without requiring a `domain` parameter.
64
+
65
+ ---
66
+
67
+ ## Quick start
68
+
69
+ ### 1. Register the cookie helper (Next.js app)
70
+
71
+ In your root layout (`app/layout.tsx`) or instrumentation file:
72
+
73
+ ```ts
74
+ import { cookies } from "next/headers";
75
+ import { registerCookiesFn } from "@econneq/gql-auth";
76
+
77
+ registerCookiesFn(cookies);
78
+ ```
79
+
80
+ This lets the Apollo client forward cookies server-side without a hard
81
+ `next/headers` import inside the package.
82
+
83
+ ---
84
+
85
+ ### 2. Server-side query (Server Component / Route Handler)
86
+
87
+ ```ts
88
+ import { queryServerGraphQL } from "@econneq/gql-auth";
89
+ import { config } from "@/config";
90
+ import { GET_USERS } from "@/graphql/queries";
91
+
92
+ // Multi-tenant
93
+ const data = await queryServerGraphQL({
94
+ query: GET_USERS,
95
+ variables: { active: true },
96
+ domain: "acme", // ← matches Subdomains[].subdomain
97
+ config,
98
+ });
99
+
100
+ // Single-tenant (omit domain)
101
+ const data = await queryServerGraphQL({
102
+ query: GET_USERS,
103
+ config,
104
+ });
105
+ ```
106
+
107
+ ### 3. Server-side mutation (Server Action)
108
+
109
+ ```ts
110
+ "use server";
111
+ import { mutateServerGraphQL } from "@econneq/gql-auth";
112
+
113
+ const result = await mutateServerGraphQL({
114
+ query: CREATE_USER_MUTATION,
115
+ variables: { input: formData },
116
+ domain: params.domain,
117
+ config,
118
+ });
119
+ ```
120
+
121
+ ---
122
+
123
+ ### 4. Client-side mutation with ApiFactory
124
+
125
+ ```tsx
126
+ "use client";
127
+ import { ApiFactory, getToken } from "@econneq/gql-auth";
128
+ import { useRouter } from "next/navigation";
129
+ import { config } from "@/config";
130
+
131
+ export function MyForm({ params }: { params: { domain: string } }) {
132
+ const router = useRouter();
133
+
134
+ const handleSubmit = async (formData: any) => {
135
+ await ApiFactory({
136
+ newData: formData,
137
+ mutationName: "createUser",
138
+ modelName: "user",
139
+ successField: "username",
140
+ query: CREATE_USER_MUTATION,
141
+ params: { domain: params.domain }, // omit for single-tenant
142
+ config,
143
+ router,
144
+ token: getToken() ?? undefined,
145
+ onAlert: ({ title, status }) => {
146
+ // Connect to your toast/alert system
147
+ console.log(title, status);
148
+ },
149
+ redirect: true,
150
+ redirectPath: "/dashboard",
151
+ });
152
+ };
153
+
154
+ return <form onSubmit={...}>...</form>;
155
+ }
156
+ ```
157
+
158
+ #### Batch mutations
159
+
160
+ Pass an array to `newData`:
161
+
162
+ ```ts
163
+ await ApiFactory({
164
+ newData: [item1, item2, item3],
165
+ mutationName: "createProduct",
166
+ modelName: "product",
167
+ // ...
168
+ });
169
+ ```
170
+
171
+ #### File uploads
172
+
173
+ ```ts
174
+ await ApiFactory({
175
+ newData: formData,
176
+ getFileMap: (item) => ({ avatar: item.avatarFile }),
177
+ // ...
178
+ });
179
+ ```
180
+
181
+ #### Return data instead of alert
182
+
183
+ ```ts
184
+ const user = await ApiFactory<{ id: string; username: string }>({
185
+ returnResponseObject: true,
186
+ // ...
187
+ });
188
+ ```
189
+
190
+ ---
191
+
192
+ ### 5. Direct upload mutation
193
+
194
+ For full control over multipart uploads:
195
+
196
+ ```ts
197
+ import { uploadGraphQLMutation } from "@econneq/gql-auth";
198
+
199
+ const response = await uploadGraphQLMutation({
200
+ query: UPLOAD_DOCUMENT_MUTATION.loc!.source.body,
201
+ variables: { title: "Report", file: null },
202
+ fileMap: { file: selectedFile },
203
+ params: { domain: "acme" },
204
+ config,
205
+ token: userToken,
206
+ });
207
+ ```
208
+
209
+ ---
210
+
211
+ ### 6. Auth utilities
212
+
213
+ #### Client-side
214
+
215
+ ```ts
216
+ import { getToken, getUser, logout } from "@econneq/gql-auth";
217
+
218
+ const token = getToken(); // cookie → localStorage
219
+ const user = getUser(); // decoded JwtPayload
220
+ logout(); // clears cookies + localStorage
221
+ ```
222
+
223
+ #### Server-side
224
+
225
+ ```ts
226
+ import {
227
+ getServerToken,
228
+ getServerUser,
229
+ getPermissions,
230
+ } from "@econneq/gql-auth";
231
+
232
+ const token = await getServerToken();
233
+ const user = await getServerUser();
234
+
235
+ // Does the user have any access to module 42?
236
+ const canRead = getPermissions(user?.permissions ?? [], 42);
237
+
238
+ // Does the user have CREATE access to module 42?
239
+ const canCreate = getPermissions(user?.permissions ?? [], 42, "C");
240
+ ```
241
+
242
+ ---
243
+
244
+ ### 7. Apollo client (advanced)
245
+
246
+ Direct access when you need a custom query/mutation outside ApiFactory:
247
+
248
+ ```ts
249
+ import { getApolloClient } from "@econneq/gql-auth";
250
+
251
+ const client = await getApolloClient({
252
+ domain: "acme", // omit for single-tenant
253
+ config,
254
+ });
255
+
256
+ const { data } = await client.query({ query: MY_QUERY });
257
+ ```
258
+
259
+ ---
260
+
261
+ ## Architecture
262
+
263
+ ```
264
+ Browser Next.js Server Backend (Docker)
265
+ ───────── ────────────── ────────────────
266
+ uploadGraphQLMutation ──► /api/graphql (proxy) ──► api:8000/graphql/
267
+ ApiFactory ──► /api/graphql (proxy) ──► api:8000/graphql/
268
+
269
+ queryServerGraphQL ────────────────────────────► http://api:8000/graphql/
270
+ mutateServerGraphQL ────────────────────────────► http://api:8000/graphql/
271
+ getApolloClient ────────────────────────────► http://api:8000/graphql/
272
+ ```
273
+
274
+ Server-side calls skip the proxy entirely and hit the backend directly
275
+ through Docker's internal network — faster and no public exposure.
276
+
277
+ ---
278
+
279
+ ## Permissions format
280
+
281
+ Permission strings encode resource + action:
282
+
283
+ ```
284
+ "42" → access resource 42 (any action)
285
+ "42C" → create on resource 42
286
+ "42R" → read on resource 42
287
+ "42U" → update on resource 42
288
+ "42D" → delete on resource 42
289
+ ```
290
+
291
+ ---
292
+
293
+ ## Changelog
294
+
295
+ ### 2.0.0
296
+ - Centralised env resolution (`env.ts`)
297
+ - Tenant resolution separated into `tenant.ts` — single-tenant fallback
298
+ - `mutateServerGraphQL` added (server-side mutations)
299
+ - `ApiFactory` typed generics, batch partial-error reporting
300
+ - `registerCookiesFn` removes hard `next/headers` dependency
301
+ - `getServerCookieString` helper exported
302
+ - `parseGraphQLErrors` exported for custom use
303
+ - Subdomains[0] fallback for single-tenant setups
304
+ - `uploadGraphQLMutation` no longer throws on missing tenant in single-tenant mode
@@ -1,30 +1,12 @@
1
- type SubmitOptions = {
2
- newData: any;
3
- editData?: any;
4
- mutationName: string;
5
- modelName: string;
6
- successField?: string;
7
- query: any;
8
- params: any;
9
- router: any;
10
- token?: string;
11
- config: {
12
- protocol: string;
13
- RootApi: string;
14
- Subdomains: any[];
15
- };
16
- onAlert: (options: {
17
- title: string;
18
- status: boolean;
19
- duration: number;
20
- }) => void;
21
- redirect?: boolean;
22
- redirectPath?: string;
23
- returnResponseField?: boolean;
24
- returnResponseObject?: boolean;
25
- reload?: boolean;
26
- getFileMap?: (item: any) => Record<string, File>;
27
- };
28
- export declare const ApiFactory: (options: SubmitOptions) => Promise<any>;
29
- export {};
1
+ import type { ApiFactoryOptions } from "./types";
2
+ /**
3
+ * Run one or more GraphQL mutations and handle the full
4
+ * response → alert / redirect / reload lifecycle.
5
+ *
6
+ * @returns
7
+ * - `returnResponseObject = true` → the raw mutation response object
8
+ * - `returnResponseField = true` → the value of `successField` from result
9
+ * - Otherwise → `void` (triggers alert + reload/redirect)
10
+ */
11
+ export declare function ApiFactory<T = any>(options: ApiFactoryOptions): Promise<T | void>;
30
12
  //# sourceMappingURL=api-factory.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"api-factory.d.ts","sourceRoot":"","sources":["../src/api-factory.ts"],"names":[],"mappings":"AAIA,KAAK,aAAa,GAAG;IACnB,OAAO,EAAE,GAAG,CAAC;IACb,QAAQ,CAAC,EAAE,GAAG,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,GAAG,CAAC;IACX,MAAM,EAAE,GAAG,CAAC;IACZ,MAAM,EAAE,GAAG,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,EAAE;QACN,QAAQ,EAAE,MAAM,CAAC;QACjB,OAAO,EAAE,MAAM,CAAC;QAChB,UAAU,EAAE,GAAG,EAAE,CAAC;KACnB,CAAC;IACF,OAAO,EAAE,CAAC,OAAO,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,OAAO,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IACjF,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;CAClD,CAAC;AAEF,eAAO,MAAM,UAAU,GAAU,SAAS,aAAa,iBA0DtD,CAAC"}
1
+ {"version":3,"file":"api-factory.d.ts","sourceRoot":"","sources":["../src/api-factory.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAiBjD;;;;;;;;GAQG;AACH,wBAAsB,UAAU,CAAC,CAAC,GAAG,GAAG,EACtC,OAAO,EAAE,iBAAiB,GACzB,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CA8GnB"}
@@ -1,68 +1,119 @@
1
- import { uploadGraphQLMutation } from "./upload-gql.js";
2
- import { removeEmptyFields } from "./utilsGeneral.js";
3
- export const ApiFactory = async (options) => {
4
- const { newData, editData, mutationName, modelName, successField = "title", query, router, params, reload = true, redirect, redirectPath, returnResponseField = false, returnResponseObject = false, getFileMap, token, config, onAlert } = options;
5
- const items = Array.isArray(newData || editData) ? (newData || editData) : [(newData || editData)];
1
+ // ============================================================
2
+ // @econneq/gql-auth ApiFactory
3
+ // ============================================================
4
+ //
5
+ // Client-side orchestrator for GraphQL mutations.
6
+ //
7
+ // Features
8
+ // ─────────
9
+ // • Single item or batch (array) mutations in one call
10
+ // • File uploads via multipart (getFileMap)
11
+ // • Multi-tenant and single-tenant support
12
+ // • Typed overloads for returnResponseField / returnResponseObject
13
+ // • Granular alert feedback for partial batch failures
14
+ // • Automatic token resolution (prop → cookie → localStorage)
15
+ // ─────────────────────────────────────────────────────────────
16
+ import { uploadGraphQLMutation } from "./upload-gql";
17
+ import { parseGraphQLErrors } from "./errors";
18
+ import { removeEmptyFields, getToken } from "./functions";
19
+ // ─── Internal helpers ────────────────────────────────────────
20
+ function resolveToken(token) {
21
+ if (token)
22
+ return token;
23
+ if (typeof window !== "undefined") {
24
+ return getToken() ?? undefined;
25
+ }
26
+ return undefined;
27
+ }
28
+ // ─── Main ────────────────────────────────────────────────────
29
+ /**
30
+ * Run one or more GraphQL mutations and handle the full
31
+ * response → alert / redirect / reload lifecycle.
32
+ *
33
+ * @returns
34
+ * - `returnResponseObject = true` → the raw mutation response object
35
+ * - `returnResponseField = true` → the value of `successField` from result
36
+ * - Otherwise → `void` (triggers alert + reload/redirect)
37
+ */
38
+ export async function ApiFactory(options) {
39
+ const { newData, editData, mutationName, modelName, successField = "title", query, router, params, reload = true, redirect, redirectPath, returnResponseField = false, returnResponseObject = false, getFileMap, config, onAlert, } = options;
40
+ const token = resolveToken(options.token);
41
+ // Support single item or batch array
42
+ const source = newData ?? editData;
43
+ const items = Array.isArray(source) ? source : [source];
6
44
  const successMessages = [];
7
45
  const errorMessages = [];
8
46
  let responseFieldData = null;
9
- for (const res of items) {
47
+ // ── Batch loop ─────────────────────────────────────────
48
+ for (const item of items) {
10
49
  try {
11
- console.log("launching e-conneq/auth-gql ......");
50
+ const cleanVariables = removeEmptyFields(item);
12
51
  const response = await uploadGraphQLMutation({
13
- query: query.loc?.source.body || "",
14
- variables: removeEmptyFields(res),
15
- fileMap: getFileMap ? getFileMap(res) : {},
16
- params, // Pass domain params for multi-tenancy
52
+ query: query?.loc?.source?.body ?? query,
53
+ variables: cleanVariables,
54
+ fileMap: getFileMap ? getFileMap(item) : {},
55
+ params,
17
56
  token,
18
- config, // Pass global URLs and Subdomain list
57
+ config,
19
58
  });
20
- console.log("response e-conneq/auth-gql ......");
21
- const result = response?.data?.[mutationName]?.[modelName];
22
- if (response?.data?.[mutationName]) {
23
- successMessages.push(result?.[successField] || "operation successful ✅");
24
- if (returnResponseObject)
25
- responseFieldData = response.data[mutationName];
26
- if (result?.id && returnResponseField)
59
+ // ── Parse success ──────────────────────────────────
60
+ const mutationPayload = response?.data?.[mutationName];
61
+ if (mutationPayload !== undefined) {
62
+ const result = mutationPayload?.[modelName];
63
+ const label = result?.[successField] ?? "Operation successful ✅";
64
+ successMessages.push(label);
65
+ if (returnResponseObject) {
66
+ responseFieldData = mutationPayload;
67
+ }
68
+ else if (returnResponseField && result?.id) {
27
69
  responseFieldData = result[successField];
28
- if (result?.id || result?.token || result === null) {
29
70
  }
30
71
  }
31
- // Error Handling
32
- if (response?.errors) {
33
- handleGraphQLErrors(response.errors, errorMessages);
72
+ // ── Parse GraphQL errors ───────────────────────────
73
+ if (response?.errors?.length) {
74
+ errorMessages.push(...parseGraphQLErrors(response.errors));
34
75
  }
35
76
  }
36
77
  catch (err) {
37
- errorMessages.push(`Error: ${err.message}`);
78
+ errorMessages.push(`Network error: ${err?.message ?? "Unknown"}`);
38
79
  }
39
80
  }
40
- // Handle Response/UI Logic
41
- if (successMessages.length > 0 && errorMessages.length === 0) {
42
- if (returnResponseObject || returnResponseField)
43
- return responseFieldData;
44
- onAlert({ title: "successfully submitted ✅", status: true, duration: 3000 });
45
- if (redirect && redirectPath)
46
- router.push(redirectPath);
47
- else if (reload)
48
- window.location.reload();
81
+ // ── Response / UI logic ────────────────────────────────
82
+ const hasSuccess = successMessages.length > 0;
83
+ const hasErrors = errorMessages.length > 0;
84
+ // Partial batch: some succeeded, some failed
85
+ if (hasSuccess && hasErrors) {
86
+ onAlert({
87
+ title: `⚠️ Partial success:\n${successMessages.join(", ")}\n\nErrors:\n${errorMessages.join("\n")}`,
88
+ status: false,
89
+ duration: 6000,
90
+ });
91
+ return;
49
92
  }
50
- else if (errorMessages.length > 0) {
93
+ if (hasErrors) {
51
94
  onAlert({
52
95
  title: `❌ Errors:\n${errorMessages.join("\n")}`,
53
96
  status: false,
54
- duration: 5000
97
+ duration: 5000,
55
98
  });
99
+ return;
100
+ }
101
+ if (hasSuccess) {
102
+ // Return data when caller wants it
103
+ if (returnResponseObject || returnResponseField) {
104
+ return responseFieldData;
105
+ }
106
+ onAlert({
107
+ title: `Successfully submitted ✅`,
108
+ status: true,
109
+ duration: 3000,
110
+ });
111
+ if (redirect && redirectPath && router) {
112
+ router.push(redirectPath);
113
+ }
114
+ else if (reload && typeof window !== "undefined") {
115
+ window.location.reload();
116
+ }
56
117
  }
57
- };
58
- // Internal Helper for Error Parsing
59
- const handleGraphQLErrors = (errors, errorList) => {
60
- errors.forEach(error => {
61
- if (error.message.includes("duplicate"))
62
- errorList.push("Record Already Exists ❌");
63
- else if (error.message.includes("Authentication"))
64
- errorList.push("Login Required ❌");
65
- else
66
- errorList.push(error.message);
67
- });
68
- };
118
+ }
119
+ //# sourceMappingURL=api-factory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-factory.js","sourceRoot":"","sources":["../src/api-factory.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ import type { JwtPayload } from "./types";
2
+ /**
3
+ * Read the JWT from the incoming request cookies.
4
+ * Returns null when no token cookie is present.
5
+ */
6
+ export declare const getServerToken: () => Promise<string | null>;
7
+ /**
8
+ * Decode the server-side JWT and return the payload.
9
+ * Returns null when no token is present or decoding fails.
10
+ */
11
+ export declare const getServerUser: () => Promise<JwtPayload | null>;
12
+ /**
13
+ * Check whether a permission list grants access to a resource.
14
+ *
15
+ * @param list - Array of permission strings (from JwtPayload.permissions)
16
+ * @param access - Numeric resource/module identifier
17
+ * @param action - Optional action suffix (e.g. "C", "R", "U", "D")
18
+ *
19
+ * @example
20
+ * // Does the user have ANY access to module 42?
21
+ * getPermissions(user.permissions, 42);
22
+ *
23
+ * // Does the user have CREATE access to module 42?
24
+ * getPermissions(user.permissions, 42, "C");
25
+ */
26
+ export declare const getPermissions: (list: string[], access: number, action?: string) => boolean;
27
+ /**
28
+ * Return the full cookie header string for forwarding to the
29
+ * backend (Django session / CSRF pass-through).
30
+ */
31
+ export declare const getServerCookieString: () => Promise<string>;
32
+ //# sourceMappingURL=authServer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authServer.d.ts","sourceRoot":"","sources":["../src/authServer.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAI1C;;;GAGG;AACH,eAAO,MAAM,cAAc,QAAa,OAAO,CAAC,MAAM,GAAG,IAAI,CAS5D,CAAC;AAIF;;;GAGG;AACH,eAAO,MAAM,aAAa,QAAa,OAAO,CAAC,UAAU,GAAG,IAAI,CAU/D,CAAC;AAIF;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,cAAc,GACzB,MAAM,MAAM,EAAE,EACd,QAAQ,MAAM,EACd,SAAS,MAAM,KACd,OAoBF,CAAC;AAIF;;;GAGG;AACH,eAAO,MAAM,qBAAqB,QAAa,OAAO,CAAC,MAAM,CAQ5D,CAAC"}
@@ -0,0 +1,93 @@
1
+ // ============================================================
2
+ // @econneq/gql-auth — Server-side auth helpers
3
+ // ============================================================
4
+ //
5
+ // These helpers are Next.js-specific (they use next/headers).
6
+ // Import them only in Server Components, Server Actions, or
7
+ // Route Handlers.
8
+ // ─────────────────────────────────────────────────────────────
9
+ "use server";
10
+ // ─── Token ───────────────────────────────────────────────────
11
+ /**
12
+ * Read the JWT from the incoming request cookies.
13
+ * Returns null when no token cookie is present.
14
+ */
15
+ export const getServerToken = async () => {
16
+ try {
17
+ const { cookies } = await import("next/headers");
18
+ const cookieStore = await cookies();
19
+ return cookieStore.get("token")?.value ?? null;
20
+ }
21
+ catch {
22
+ // next/headers not available outside of a Next.js context
23
+ return null;
24
+ }
25
+ };
26
+ // ─── User ─────────────────────────────────────────────────────
27
+ /**
28
+ * Decode the server-side JWT and return the payload.
29
+ * Returns null when no token is present or decoding fails.
30
+ */
31
+ export const getServerUser = async () => {
32
+ const token = await getServerToken();
33
+ if (!token)
34
+ return null;
35
+ try {
36
+ const { jwtDecode } = await import("jwt-decode");
37
+ return jwtDecode(token);
38
+ }
39
+ catch {
40
+ return null;
41
+ }
42
+ };
43
+ // ─── Permissions ─────────────────────────────────────────────
44
+ /**
45
+ * Check whether a permission list grants access to a resource.
46
+ *
47
+ * @param list - Array of permission strings (from JwtPayload.permissions)
48
+ * @param access - Numeric resource/module identifier
49
+ * @param action - Optional action suffix (e.g. "C", "R", "U", "D")
50
+ *
51
+ * @example
52
+ * // Does the user have ANY access to module 42?
53
+ * getPermissions(user.permissions, 42);
54
+ *
55
+ * // Does the user have CREATE access to module 42?
56
+ * getPermissions(user.permissions, 42, "C");
57
+ */
58
+ export const getPermissions = (list, access, action) => {
59
+ if (!list?.length)
60
+ return false;
61
+ try {
62
+ return list.some((entry) => {
63
+ if (access && !action) {
64
+ // Match "42", "42C", "42R", etc. — but NOT "420"
65
+ const regex = new RegExp(`^${access}(?!\\d)`);
66
+ return regex.test(entry);
67
+ }
68
+ if (access && action) {
69
+ return entry === `${access}${action}`;
70
+ }
71
+ return false;
72
+ });
73
+ }
74
+ catch {
75
+ return false;
76
+ }
77
+ };
78
+ // ─── Cookie string ────────────────────────────────────────────
79
+ /**
80
+ * Return the full cookie header string for forwarding to the
81
+ * backend (Django session / CSRF pass-through).
82
+ */
83
+ export const getServerCookieString = async () => {
84
+ try {
85
+ const { cookies } = await import("next/headers");
86
+ const cookieStore = await cookies();
87
+ return cookieStore.toString();
88
+ }
89
+ catch {
90
+ return "";
91
+ }
92
+ };
93
+ //# sourceMappingURL=authServer.js.map