@zitadel/sdk-next 0.1.0-alpha.9 → 1.0.0-alpha.21
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 +134 -26
- package/dist/auth.d.ts +54 -13
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +154 -7
- package/dist/client.d.ts +23 -2
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +21 -9
- package/dist/context.d.ts +21 -10
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +24 -8
- package/dist/index.d.ts +16 -9
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -24
- package/dist/jsx.d.ts +5 -0
- package/dist/lib/jwt.d.ts +9 -0
- package/dist/lib/jwt.d.ts.map +1 -0
- package/dist/lib/jwt.js +7 -0
- package/dist/middleware.d.ts +9 -10
- package/dist/middleware.d.ts.map +1 -0
- package/dist/middleware.js +294 -9
- package/dist/provider.d.ts +52 -0
- package/dist/provider.d.ts.map +1 -0
- package/dist/provider.js +75 -0
- package/dist/react.d.ts +26 -0
- package/dist/react.d.ts.map +1 -0
- package/dist/react.js +24 -0
- package/dist/server.d.ts +12 -2
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +10 -7
- package/dist/session.d.ts +61 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +116 -0
- package/dist/types.d.ts +8 -3
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -1
- package/dist/useAuth.d.ts +17 -5
- package/dist/useAuth.d.ts.map +1 -0
- package/dist/useAuth.js +18 -7
- package/package.json +28 -13
- package/dist/chunk-2BFQLJQE.js +0 -27
- package/dist/chunk-4KENHIG4.js +0 -222
- package/dist/chunk-6F4PWJZI.js +0 -0
- package/dist/chunk-B7S6XMT3.js +0 -41
- package/dist/chunk-OCZMYSFX.js +0 -13
- package/dist/chunk-XTCHTAIQ.js +0 -12
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { ClientAuthResult } from "./types.js";
|
|
2
|
+
/** Options for {@link getSession}. */
|
|
3
|
+
export type GetSessionOptions = {
|
|
4
|
+
/**
|
|
5
|
+
* Proxy path the scaffolded request boundary forwards to the Zitadel
|
|
6
|
+
* backend. Defaults to the path from `configureZitadel()` when it has run
|
|
7
|
+
* on this page, else `"/__nextgen"` (the scaffold default).
|
|
8
|
+
*/
|
|
9
|
+
proxyPath?: string;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Reads the current session state from the browser — the supported way for
|
|
13
|
+
* an app's own UI (header navigation, account menus) to know whether a user
|
|
14
|
+
* is signed in and who they are.
|
|
15
|
+
*
|
|
16
|
+
* Fetches the same-origin `{proxyPath}/sessions/me` with credentials and
|
|
17
|
+
* `cache: "no-store"` — the same read the `<zitadel-session>` card performs —
|
|
18
|
+
* so the answer is the server's, not a client-side guess. Works on any page:
|
|
19
|
+
* unlike server-side `auth()`, it does not require the route to be covered by
|
|
20
|
+
* the middleware `matcher` (only the proxy path itself must be matched, which
|
|
21
|
+
* the scaffolded request boundary always does), and it does not require
|
|
22
|
+
* `configureZitadel()` to have run.
|
|
23
|
+
*
|
|
24
|
+
* - `200` with an authenticated user → `{ isAuthenticated: true, session }`
|
|
25
|
+
* with the client-safe identity (`userId`, `email`, `name` — no token).
|
|
26
|
+
* - `200` for an anonymous session, `401` with `auth.unauthorized`, or `404`
|
|
27
|
+
* with `sess.not_found` → signed out.
|
|
28
|
+
* - Any other response throws — including a framework's HTML 404 page from
|
|
29
|
+
* a misrouted proxy: a failing proxy is a misconfiguration and must not
|
|
30
|
+
* silently render as "signed out". Treat a rejection as "unknown" —
|
|
31
|
+
* distinct from signed-out — as the example does.
|
|
32
|
+
*
|
|
33
|
+
* ```tsx
|
|
34
|
+
* "use client";
|
|
35
|
+
* import { getSession, type ClientAuthResult } from "@zitadel/sdk-next/session";
|
|
36
|
+
*
|
|
37
|
+
* export function HeaderNav() {
|
|
38
|
+
* // undefined = not yet known — render neutral chrome, not "Sign in".
|
|
39
|
+
* const [auth, setAuth] = useState<ClientAuthResult>();
|
|
40
|
+
* const [failed, setFailed] = useState(false);
|
|
41
|
+
* useEffect(() => {
|
|
42
|
+
* getSession().then(setAuth, () => setFailed(true));
|
|
43
|
+
* }, []);
|
|
44
|
+
* if (failed) return <span role="alert">Session unavailable</span>;
|
|
45
|
+
* if (!auth) return null;
|
|
46
|
+
* return auth.isAuthenticated
|
|
47
|
+
* ? <a href="/profile">{auth.session.display ?? auth.session.identifier ?? "Account"}</a>
|
|
48
|
+
* : <a href="/login">Sign in</a>;
|
|
49
|
+
* }
|
|
50
|
+
* ```
|
|
51
|
+
*
|
|
52
|
+
* The transitions need no extra wiring in the scaffolded posture: sign-in
|
|
53
|
+
* (`post-sign-in-url`) and sign-out (`post-sign-out-url`) both navigate, so
|
|
54
|
+
* chrome re-reads on the next page load. To react in place instead, listen
|
|
55
|
+
* for the widgets' `zitadel-signout` / `zitadel-flow-complete` events.
|
|
56
|
+
*
|
|
57
|
+
* @returns The current {@link ClientAuthResult}.
|
|
58
|
+
*/
|
|
59
|
+
export declare function getSession(options?: GetSessionOptions): Promise<ClientAuthResult>;
|
|
60
|
+
export type { ClientAuthResult, ClientAuthState, ClientSession } from "./types.js";
|
|
61
|
+
//# sourceMappingURL=session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAKnD,sCAAsC;AACtC,MAAM,MAAM,iBAAiB,GAAG;IAC9B;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AACH,wBAAsB,UAAU,CAAC,OAAO,GAAE,iBAAsB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CA2D3F;AAED,YAAY,EAAE,gBAAgB,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC"}
|
package/dist/session.js
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { getZitadelConfig } from "@zitadel/api/config";
|
|
2
|
+
/** Matches the `configureZitadel()` default so zero-config apps agree with the scaffold. */
|
|
3
|
+
const DEFAULT_PROXY_PATH = "/__nextgen";
|
|
4
|
+
/**
|
|
5
|
+
* Reads the current session state from the browser — the supported way for
|
|
6
|
+
* an app's own UI (header navigation, account menus) to know whether a user
|
|
7
|
+
* is signed in and who they are.
|
|
8
|
+
*
|
|
9
|
+
* Fetches the same-origin `{proxyPath}/sessions/me` with credentials and
|
|
10
|
+
* `cache: "no-store"` — the same read the `<zitadel-session>` card performs —
|
|
11
|
+
* so the answer is the server's, not a client-side guess. Works on any page:
|
|
12
|
+
* unlike server-side `auth()`, it does not require the route to be covered by
|
|
13
|
+
* the middleware `matcher` (only the proxy path itself must be matched, which
|
|
14
|
+
* the scaffolded request boundary always does), and it does not require
|
|
15
|
+
* `configureZitadel()` to have run.
|
|
16
|
+
*
|
|
17
|
+
* - `200` with an authenticated user → `{ isAuthenticated: true, session }`
|
|
18
|
+
* with the client-safe identity (`userId`, `email`, `name` — no token).
|
|
19
|
+
* - `200` for an anonymous session, `401` with `auth.unauthorized`, or `404`
|
|
20
|
+
* with `sess.not_found` → signed out.
|
|
21
|
+
* - Any other response throws — including a framework's HTML 404 page from
|
|
22
|
+
* a misrouted proxy: a failing proxy is a misconfiguration and must not
|
|
23
|
+
* silently render as "signed out". Treat a rejection as "unknown" —
|
|
24
|
+
* distinct from signed-out — as the example does.
|
|
25
|
+
*
|
|
26
|
+
* ```tsx
|
|
27
|
+
* "use client";
|
|
28
|
+
* import { getSession, type ClientAuthResult } from "@zitadel/sdk-next/session";
|
|
29
|
+
*
|
|
30
|
+
* export function HeaderNav() {
|
|
31
|
+
* // undefined = not yet known — render neutral chrome, not "Sign in".
|
|
32
|
+
* const [auth, setAuth] = useState<ClientAuthResult>();
|
|
33
|
+
* const [failed, setFailed] = useState(false);
|
|
34
|
+
* useEffect(() => {
|
|
35
|
+
* getSession().then(setAuth, () => setFailed(true));
|
|
36
|
+
* }, []);
|
|
37
|
+
* if (failed) return <span role="alert">Session unavailable</span>;
|
|
38
|
+
* if (!auth) return null;
|
|
39
|
+
* return auth.isAuthenticated
|
|
40
|
+
* ? <a href="/profile">{auth.session.display ?? auth.session.identifier ?? "Account"}</a>
|
|
41
|
+
* : <a href="/login">Sign in</a>;
|
|
42
|
+
* }
|
|
43
|
+
* ```
|
|
44
|
+
*
|
|
45
|
+
* The transitions need no extra wiring in the scaffolded posture: sign-in
|
|
46
|
+
* (`post-sign-in-url`) and sign-out (`post-sign-out-url`) both navigate, so
|
|
47
|
+
* chrome re-reads on the next page load. To react in place instead, listen
|
|
48
|
+
* for the widgets' `zitadel-signout` / `zitadel-flow-complete` events.
|
|
49
|
+
*
|
|
50
|
+
* @returns The current {@link ClientAuthResult}.
|
|
51
|
+
*/
|
|
52
|
+
export async function getSession(options = {}) {
|
|
53
|
+
if (typeof window === "undefined") {
|
|
54
|
+
throw new Error("[nextgen] getSession() reads the session from the browser. " +
|
|
55
|
+
"In Server Components and Route Handlers use auth() from @zitadel/sdk-next/server " +
|
|
56
|
+
"(requires the route to be covered by the middleware matcher).");
|
|
57
|
+
}
|
|
58
|
+
// Strip trailing slashes so "/__nextgen/" doesn't produce a double-slash
|
|
59
|
+
// URL that misses the request-boundary matcher — same normalization the
|
|
60
|
+
// typed API client applies to its base URL.
|
|
61
|
+
let proxyPath = options.proxyPath ?? getZitadelConfig()?.proxyPath ?? DEFAULT_PROXY_PATH;
|
|
62
|
+
while (proxyPath.endsWith("/")) {
|
|
63
|
+
proxyPath = proxyPath.slice(0, -1);
|
|
64
|
+
}
|
|
65
|
+
const response = await fetch(`${proxyPath}/sessions/me`, {
|
|
66
|
+
cache: "no-store",
|
|
67
|
+
credentials: "include",
|
|
68
|
+
headers: { accept: "application/json" },
|
|
69
|
+
});
|
|
70
|
+
// Only the backend's canonical error envelope is definitive signed-out.
|
|
71
|
+
// A framework route, gateway, or WAF can also return 401/404; accepting the
|
|
72
|
+
// status or content type alone would turn a broken proxy into signed-out.
|
|
73
|
+
if (response.status === 401 || response.status === 404) {
|
|
74
|
+
const error = await readErrorEnvelope(response);
|
|
75
|
+
const isSignedOut = (response.status === 401 && error?.code === "auth.unauthorized") ||
|
|
76
|
+
(response.status === 404 && error?.code === "sess.not_found");
|
|
77
|
+
if (isSignedOut) {
|
|
78
|
+
return { isAuthenticated: false, session: null };
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (!response.ok) {
|
|
82
|
+
throw new Error(`[nextgen] Session read failed: HTTP ${response.status} from ${proxyPath}/sessions/me`);
|
|
83
|
+
}
|
|
84
|
+
const session = (await response.json());
|
|
85
|
+
// An anonymous session (no verified user factor yet) has no user_id —
|
|
86
|
+
// for app chrome that is "not signed in".
|
|
87
|
+
if (!session.user_id) {
|
|
88
|
+
return { isAuthenticated: false, session: null };
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
isAuthenticated: true,
|
|
92
|
+
session: {
|
|
93
|
+
userId: session.user_id,
|
|
94
|
+
identifier: session.user?.identifier ?? null,
|
|
95
|
+
identifierProperty: session.user?.identifier_property ?? null,
|
|
96
|
+
display: session.user?.display ?? null,
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
async function readErrorEnvelope(response) {
|
|
101
|
+
try {
|
|
102
|
+
const body = (await response.json());
|
|
103
|
+
if (typeof body === "object" &&
|
|
104
|
+
body !== null &&
|
|
105
|
+
"code" in body &&
|
|
106
|
+
typeof body.code === "string" &&
|
|
107
|
+
"message" in body &&
|
|
108
|
+
typeof body.message === "string") {
|
|
109
|
+
return { code: body.code, message: body.message };
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
// The caller reports the HTTP failure below; parsing details are untrusted.
|
|
114
|
+
}
|
|
115
|
+
return undefined;
|
|
116
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Re-exports shared SDK types from `@zitadel/sdk-core`.
|
|
3
|
+
*
|
|
4
|
+
* These types are defined once in sdk-core and shared by both sdk-next
|
|
5
|
+
* and sdk-nuxt.
|
|
6
|
+
*/
|
|
7
|
+
export type { NextgenSession, AuthState, UnauthState, AuthResult, NextgenMiddlewareOptions, ClientSession, ClientAuthState, ClientAuthResult, } from "@zitadel/sdk-core/middleware";
|
|
8
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,YAAY,EACV,cAAc,EACd,SAAS,EACT,WAAW,EACX,UAAU,EACV,wBAAwB,EAGxB,aAAa,EACb,eAAe,EACf,gBAAgB,GACjB,MAAM,8BAA8B,CAAC"}
|
package/dist/types.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
export {};
|
package/dist/useAuth.d.ts
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import type { ClientAuthResult } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Reads the auth state seeded by {@link NextgenProvider} in a client
|
|
4
|
+
* component.
|
|
5
|
+
*
|
|
6
|
+
* Returns the client-safe {@link ClientAuthResult} (`userId` / `email` /
|
|
7
|
+
* `name`) — the raw session token is stripped server-side by the provider
|
|
8
|
+
* and is never available client-side. This is the same shape sdk-nuxt's
|
|
9
|
+
* `useAuth()` and `getSession()` return.
|
|
10
|
+
*
|
|
11
|
+
* The value reflects what the server knew when the page rendered. For chrome
|
|
12
|
+
* on pages outside the middleware `matcher` (where `auth()` reports signed
|
|
13
|
+
* out), read the session live with `getSession()` from
|
|
14
|
+
* `@zitadel/sdk-next/session` instead.
|
|
15
|
+
*/
|
|
16
|
+
export declare function useAuth(): ClientAuthResult;
|
|
17
|
+
//# sourceMappingURL=useAuth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useAuth.d.ts","sourceRoot":"","sources":["../src/useAuth.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAInD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,OAAO,IAAI,gBAAgB,CAE1C"}
|
package/dist/useAuth.js
CHANGED
|
@@ -1,8 +1,19 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
2
|
+
import { useAuthContext } from "./context.js";
|
|
3
|
+
/**
|
|
4
|
+
* Reads the auth state seeded by {@link NextgenProvider} in a client
|
|
5
|
+
* component.
|
|
6
|
+
*
|
|
7
|
+
* Returns the client-safe {@link ClientAuthResult} (`userId` / `email` /
|
|
8
|
+
* `name`) — the raw session token is stripped server-side by the provider
|
|
9
|
+
* and is never available client-side. This is the same shape sdk-nuxt's
|
|
10
|
+
* `useAuth()` and `getSession()` return.
|
|
11
|
+
*
|
|
12
|
+
* The value reflects what the server knew when the page rendered. For chrome
|
|
13
|
+
* on pages outside the middleware `matcher` (where `auth()` reports signed
|
|
14
|
+
* out), read the session live with `getSession()` from
|
|
15
|
+
* `@zitadel/sdk-next/session` instead.
|
|
16
|
+
*/
|
|
17
|
+
export function useAuth() {
|
|
18
|
+
return useAuthContext();
|
|
19
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zitadel/sdk-next",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.0.0-alpha.21",
|
|
4
4
|
"description": "Next.js helpers and mock auth UI for Zitadel",
|
|
5
5
|
"homepage": "https://github.com/zitadel/nextgen/tree/main/packages/sdk-next#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -16,6 +16,11 @@
|
|
|
16
16
|
"dist"
|
|
17
17
|
],
|
|
18
18
|
"type": "module",
|
|
19
|
+
"sideEffects": [
|
|
20
|
+
"./dist/client.js",
|
|
21
|
+
"./dist/auth.js",
|
|
22
|
+
"./dist/provider.js"
|
|
23
|
+
],
|
|
19
24
|
"exports": {
|
|
20
25
|
".": {
|
|
21
26
|
"types": "./dist/index.d.ts",
|
|
@@ -29,9 +34,20 @@
|
|
|
29
34
|
"types": "./dist/client.d.ts",
|
|
30
35
|
"import": "./dist/client.js"
|
|
31
36
|
},
|
|
37
|
+
"./react": {
|
|
38
|
+
"types": "./dist/react.d.ts",
|
|
39
|
+
"import": "./dist/react.js"
|
|
40
|
+
},
|
|
32
41
|
"./middleware": {
|
|
33
42
|
"types": "./dist/middleware.d.ts",
|
|
34
43
|
"import": "./dist/middleware.js"
|
|
44
|
+
},
|
|
45
|
+
"./jsx": {
|
|
46
|
+
"types": "./dist/jsx.d.ts"
|
|
47
|
+
},
|
|
48
|
+
"./session": {
|
|
49
|
+
"types": "./dist/session.d.ts",
|
|
50
|
+
"import": "./dist/session.js"
|
|
35
51
|
}
|
|
36
52
|
},
|
|
37
53
|
"publishConfig": {
|
|
@@ -39,23 +55,21 @@
|
|
|
39
55
|
},
|
|
40
56
|
"dependencies": {
|
|
41
57
|
"server-only": "^0.0.1",
|
|
42
|
-
"@zitadel/
|
|
43
|
-
"@zitadel/
|
|
44
|
-
"@zitadel/
|
|
58
|
+
"@zitadel/sdk-core": "1.0.0-alpha.21",
|
|
59
|
+
"@zitadel/api": "1.0.0-alpha.21",
|
|
60
|
+
"@zitadel/components": "1.0.0-alpha.21"
|
|
45
61
|
},
|
|
46
62
|
"peerDependencies": {
|
|
47
|
-
"next": ">=
|
|
63
|
+
"next": ">=15",
|
|
48
64
|
"react": ">=18",
|
|
49
65
|
"react-dom": ">=18"
|
|
50
66
|
},
|
|
51
67
|
"devDependencies": {
|
|
52
68
|
"@eslint/js": "^9.0.0",
|
|
53
|
-
"tsup": "^8.3.5",
|
|
54
|
-
"typescript": "^5.7.3",
|
|
55
69
|
"@eslint/json": "^0.11.0",
|
|
56
70
|
"@eslint/markdown": "^6.0.0",
|
|
57
|
-
"@testing-library/react": "^16.
|
|
58
|
-
"@testing-library/user-event": "^14.
|
|
71
|
+
"@testing-library/react": "^16.3.2",
|
|
72
|
+
"@testing-library/user-event": "^14.6.1",
|
|
59
73
|
"@types/react": "^19.2.14",
|
|
60
74
|
"eslint": "^9.0.0",
|
|
61
75
|
"eslint-config-prettier": "^10.0.0",
|
|
@@ -67,15 +81,16 @@
|
|
|
67
81
|
"eslint-plugin-react": "^7.37.0",
|
|
68
82
|
"eslint-plugin-react-hooks": "^5.0.0",
|
|
69
83
|
"eslint-plugin-testing-library": "^7.0.0",
|
|
70
|
-
"jsdom": "^
|
|
71
|
-
"next": "^16.2.
|
|
84
|
+
"jsdom": "^29.0.2",
|
|
85
|
+
"next": "^16.2.11",
|
|
72
86
|
"prettier": "^3.0.0",
|
|
87
|
+
"typescript": "^5.7.3",
|
|
73
88
|
"typescript-eslint": "^8.0.0",
|
|
74
89
|
"vitest": "^3.0.0"
|
|
75
90
|
},
|
|
76
91
|
"scripts": {
|
|
77
|
-
"build": "
|
|
78
|
-
"typecheck": "tsc --
|
|
92
|
+
"build": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\" && tsc -p tsconfig.build.json && node -e \"require('node:fs').copyFileSync('src/jsx.d.ts', 'dist/jsx.d.ts')\"",
|
|
93
|
+
"typecheck": "tsc --build tsconfig.json",
|
|
79
94
|
"test": "vitest run --passWithNoTests",
|
|
80
95
|
"lint": "eslint ."
|
|
81
96
|
}
|
package/dist/chunk-2BFQLJQE.js
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
// src/context.tsx
|
|
2
|
-
import { createContext, useContext } from "react";
|
|
3
|
-
import { jsx } from "react/jsx-runtime";
|
|
4
|
-
var defaultValue = { isAuthenticated: false, session: null };
|
|
5
|
-
var NextgenAuthContext = createContext(defaultValue);
|
|
6
|
-
function NextgenProvider({
|
|
7
|
-
session,
|
|
8
|
-
children
|
|
9
|
-
}) {
|
|
10
|
-
let value;
|
|
11
|
-
if (!session) {
|
|
12
|
-
value = { isAuthenticated: false, session: null };
|
|
13
|
-
} else if ("isAuthenticated" in session) {
|
|
14
|
-
value = session;
|
|
15
|
-
} else {
|
|
16
|
-
value = { isAuthenticated: true, session };
|
|
17
|
-
}
|
|
18
|
-
return /* @__PURE__ */ jsx(NextgenAuthContext.Provider, { value, children });
|
|
19
|
-
}
|
|
20
|
-
function useAuthContext() {
|
|
21
|
-
return useContext(NextgenAuthContext);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export {
|
|
25
|
-
NextgenProvider,
|
|
26
|
-
useAuthContext
|
|
27
|
-
};
|
package/dist/chunk-4KENHIG4.js
DELETED
|
@@ -1,222 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
base64UrlDecode,
|
|
3
|
-
verifyJwt
|
|
4
|
-
} from "./chunk-OCZMYSFX.js";
|
|
5
|
-
|
|
6
|
-
// src/middleware.ts
|
|
7
|
-
import {
|
|
8
|
-
HOP_BY_HOP,
|
|
9
|
-
INTERNAL_HEADERS,
|
|
10
|
-
filterResponseHeaders,
|
|
11
|
-
matchesRoutes
|
|
12
|
-
} from "@zitadel/sdk-core/middleware";
|
|
13
|
-
import { NextResponse } from "next/server";
|
|
14
|
-
function tunnelHeaders(req, extra) {
|
|
15
|
-
const headers = new Headers(req.headers);
|
|
16
|
-
headers.delete("x-middleware-override-headers");
|
|
17
|
-
const injectedNames = [];
|
|
18
|
-
for (const [name, value] of Object.entries(extra)) {
|
|
19
|
-
headers.set(name, value);
|
|
20
|
-
headers.set(`x-middleware-request-${name}`, value);
|
|
21
|
-
injectedNames.push(name);
|
|
22
|
-
}
|
|
23
|
-
headers.set("x-middleware-override-headers", injectedNames.join(","));
|
|
24
|
-
return headers;
|
|
25
|
-
}
|
|
26
|
-
async function nextgenMiddleware(req, options = {}) {
|
|
27
|
-
const {
|
|
28
|
-
url = process.env.ZITADEL_URL ?? "http://localhost:8080",
|
|
29
|
-
proxyPath = "/__nextgen",
|
|
30
|
-
protectedRoutes = [],
|
|
31
|
-
ignoredRoutes = [],
|
|
32
|
-
loginPath = "/login",
|
|
33
|
-
allowedAlgorithms = ["RS256", "ES256"],
|
|
34
|
-
clockSkewMs = 5e3,
|
|
35
|
-
audience,
|
|
36
|
-
allowedTokenTypes = ["JWT", "at+JWT"],
|
|
37
|
-
jwksTimeoutMs,
|
|
38
|
-
proxyTimeoutMs = 5e3,
|
|
39
|
-
onExchangeResponse
|
|
40
|
-
} = options;
|
|
41
|
-
if (!loginPath.startsWith("/") || loginPath.startsWith("//")) {
|
|
42
|
-
throw new Error(
|
|
43
|
-
`[nextgen] loginPath must be a relative path starting with a single "/". Received: "${loginPath}". Using an absolute or protocol-relative URL would allow open-redirect attacks.`
|
|
44
|
-
);
|
|
45
|
-
}
|
|
46
|
-
const { pathname } = new URL(req.url);
|
|
47
|
-
if (matchesRoutes(pathname, ignoredRoutes)) {
|
|
48
|
-
const headers = tunnelHeaders(req, { "x-nextgen-auth-token": "" });
|
|
49
|
-
return NextResponse.next({ request: { headers } });
|
|
50
|
-
}
|
|
51
|
-
if (pathname === proxyPath || pathname.startsWith(`${proxyPath}/`)) {
|
|
52
|
-
return proxyRequest(
|
|
53
|
-
req,
|
|
54
|
-
url,
|
|
55
|
-
proxyPath,
|
|
56
|
-
proxyTimeoutMs,
|
|
57
|
-
onExchangeResponse
|
|
58
|
-
);
|
|
59
|
-
}
|
|
60
|
-
return handleAuth(req, {
|
|
61
|
-
url,
|
|
62
|
-
protectedRoutes,
|
|
63
|
-
loginPath,
|
|
64
|
-
allowedAlgorithms,
|
|
65
|
-
clockSkewMs,
|
|
66
|
-
audience,
|
|
67
|
-
allowedTokenTypes,
|
|
68
|
-
jwksTimeoutMs,
|
|
69
|
-
pathname
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
var DECODER = new TextDecoder();
|
|
73
|
-
function isJwtShaped(token) {
|
|
74
|
-
const parts = token.split(".");
|
|
75
|
-
if (parts.length < 3 || !parts[0]) return false;
|
|
76
|
-
try {
|
|
77
|
-
const header = JSON.parse(
|
|
78
|
-
DECODER.decode(base64UrlDecode(parts[0]))
|
|
79
|
-
);
|
|
80
|
-
return typeof header?.alg === "string" && !("enc" in header);
|
|
81
|
-
} catch {
|
|
82
|
-
return false;
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
async function validateOpaqueSessionToken(token, issuerUrl, timeoutMs) {
|
|
86
|
-
try {
|
|
87
|
-
const res = await fetch(`${issuerUrl}/sessions/me`, {
|
|
88
|
-
method: "GET",
|
|
89
|
-
headers: { cookie: `__nextgen_session=${token}` },
|
|
90
|
-
signal: AbortSignal.timeout(timeoutMs)
|
|
91
|
-
});
|
|
92
|
-
return res.ok;
|
|
93
|
-
} catch {
|
|
94
|
-
return false;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
async function proxyRequest(req, authUrl, proxyPath, proxyTimeoutMs, onExchangeResponse) {
|
|
98
|
-
const url = new URL(req.url);
|
|
99
|
-
const suffix = url.pathname.slice(proxyPath.length);
|
|
100
|
-
const target = `${authUrl}${suffix}${url.search}`;
|
|
101
|
-
const upstreamHeaders = new Headers();
|
|
102
|
-
for (const [key, value] of req.headers.entries()) {
|
|
103
|
-
const lower = key.toLowerCase();
|
|
104
|
-
if (!HOP_BY_HOP.has(lower) && !INTERNAL_HEADERS.has(lower)) {
|
|
105
|
-
upstreamHeaders.set(key, value);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
const directIp = req.ip ?? req.headers.get("x-real-ip");
|
|
109
|
-
if (directIp) {
|
|
110
|
-
const existingXff = upstreamHeaders.get("x-forwarded-for");
|
|
111
|
-
upstreamHeaders.set(
|
|
112
|
-
"x-forwarded-for",
|
|
113
|
-
existingXff ? `${existingXff}, ${directIp}` : directIp
|
|
114
|
-
);
|
|
115
|
-
}
|
|
116
|
-
if (!upstreamHeaders.has("x-forwarded-host")) {
|
|
117
|
-
upstreamHeaders.set("x-forwarded-host", url.host);
|
|
118
|
-
}
|
|
119
|
-
if (!upstreamHeaders.has("x-forwarded-proto")) {
|
|
120
|
-
upstreamHeaders.set("x-forwarded-proto", url.protocol.replace(":", ""));
|
|
121
|
-
}
|
|
122
|
-
const hasBody = !["GET", "HEAD"].includes(req.method);
|
|
123
|
-
const isExchangeRequest = req.method === "POST" && suffix.startsWith("/sessions/exchange");
|
|
124
|
-
if (isExchangeRequest && !upstreamHeaders.has("authorization")) {
|
|
125
|
-
const projectId = url.searchParams.get("project_id");
|
|
126
|
-
if (projectId) {
|
|
127
|
-
upstreamHeaders.set("authorization", `Bearer sk_${projectId}`);
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
const bodyBuffer = hasBody ? await req.arrayBuffer() : void 0;
|
|
131
|
-
const upstream = await fetch(target, {
|
|
132
|
-
method: req.method,
|
|
133
|
-
headers: upstreamHeaders,
|
|
134
|
-
body: bodyBuffer,
|
|
135
|
-
redirect: "manual",
|
|
136
|
-
signal: AbortSignal.timeout(proxyTimeoutMs)
|
|
137
|
-
});
|
|
138
|
-
const responseHeaders = filterResponseHeaders(upstream.headers);
|
|
139
|
-
const setCookies = upstream.headers.getSetCookie?.() ?? [];
|
|
140
|
-
for (const cookie of setCookies) {
|
|
141
|
-
responseHeaders.append("set-cookie", cookie);
|
|
142
|
-
}
|
|
143
|
-
let response = new Response(upstream.body, {
|
|
144
|
-
status: upstream.status,
|
|
145
|
-
headers: responseHeaders
|
|
146
|
-
});
|
|
147
|
-
if (isExchangeRequest && onExchangeResponse) {
|
|
148
|
-
response = await onExchangeResponse(response);
|
|
149
|
-
}
|
|
150
|
-
return response;
|
|
151
|
-
}
|
|
152
|
-
async function handleAuth(req, opts) {
|
|
153
|
-
const {
|
|
154
|
-
url,
|
|
155
|
-
protectedRoutes,
|
|
156
|
-
loginPath,
|
|
157
|
-
allowedAlgorithms,
|
|
158
|
-
clockSkewMs,
|
|
159
|
-
audience,
|
|
160
|
-
allowedTokenTypes,
|
|
161
|
-
jwksTimeoutMs,
|
|
162
|
-
pathname
|
|
163
|
-
} = opts;
|
|
164
|
-
const authHeader = req.headers.get("authorization");
|
|
165
|
-
const bearerToken = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : null;
|
|
166
|
-
const cookieToken = req.cookies.get("__nextgen_session")?.value ?? null;
|
|
167
|
-
const token = bearerToken ?? cookieToken;
|
|
168
|
-
const payload = token ? await verifyJwt(token, {
|
|
169
|
-
issuerUrl: url,
|
|
170
|
-
allowedAlgorithms,
|
|
171
|
-
clockSkewMs,
|
|
172
|
-
audience,
|
|
173
|
-
allowedTokenTypes,
|
|
174
|
-
jwksTimeoutMs
|
|
175
|
-
}) : null;
|
|
176
|
-
if (payload && token && payload.sub) {
|
|
177
|
-
const tunnelled2 = tunnelHeaders(req, { "x-nextgen-auth-token": token });
|
|
178
|
-
return NextResponse.next({ request: { headers: tunnelled2 } });
|
|
179
|
-
}
|
|
180
|
-
if (!payload && cookieToken && !isJwtShaped(cookieToken)) {
|
|
181
|
-
const isValid = await validateOpaqueSessionToken(
|
|
182
|
-
cookieToken,
|
|
183
|
-
url,
|
|
184
|
-
jwksTimeoutMs ?? 5e3
|
|
185
|
-
);
|
|
186
|
-
if (isValid) {
|
|
187
|
-
const tunnelled2 = tunnelHeaders(req, {
|
|
188
|
-
"x-nextgen-auth-token": cookieToken
|
|
189
|
-
});
|
|
190
|
-
return NextResponse.next({ request: { headers: tunnelled2 } });
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
const tunnelled = tunnelHeaders(req, { "x-nextgen-auth-token": "" });
|
|
194
|
-
const staleNextgenCookies = req.cookies.getAll().filter((c) => c.name.startsWith("__nextgen"));
|
|
195
|
-
if (matchesRoutes(pathname, protectedRoutes)) {
|
|
196
|
-
const loginUrl = new URL(loginPath, req.url);
|
|
197
|
-
loginUrl.searchParams.set("next", pathname);
|
|
198
|
-
const redirect = NextResponse.redirect(loginUrl, { status: 302 });
|
|
199
|
-
for (const cookie of staleNextgenCookies) {
|
|
200
|
-
redirect.cookies.delete(cookie.name);
|
|
201
|
-
}
|
|
202
|
-
return redirect;
|
|
203
|
-
}
|
|
204
|
-
const response = NextResponse.next({ request: { headers: tunnelled } });
|
|
205
|
-
for (const cookie of staleNextgenCookies) {
|
|
206
|
-
response.cookies.delete(cookie.name);
|
|
207
|
-
}
|
|
208
|
-
return response;
|
|
209
|
-
}
|
|
210
|
-
function createProxy(config, options = {}) {
|
|
211
|
-
const mergedOptions = {
|
|
212
|
-
...options,
|
|
213
|
-
proxyPath: config.proxyPath,
|
|
214
|
-
url: config.url
|
|
215
|
-
};
|
|
216
|
-
return (req) => nextgenMiddleware(req, mergedOptions);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
export {
|
|
220
|
-
nextgenMiddleware,
|
|
221
|
-
createProxy
|
|
222
|
-
};
|
package/dist/chunk-6F4PWJZI.js
DELETED
|
File without changes
|
package/dist/chunk-B7S6XMT3.js
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
decodeJwt
|
|
3
|
-
} from "./chunk-OCZMYSFX.js";
|
|
4
|
-
|
|
5
|
-
// src/auth.ts
|
|
6
|
-
import { headers } from "next/headers";
|
|
7
|
-
async function auth() {
|
|
8
|
-
const headerStore = await headers();
|
|
9
|
-
const token = headerStore.get("x-nextgen-auth-token");
|
|
10
|
-
if (!token) {
|
|
11
|
-
return { isAuthenticated: false, session: null };
|
|
12
|
-
}
|
|
13
|
-
try {
|
|
14
|
-
const { payload } = decodeJwt(token);
|
|
15
|
-
if (payload.sub) {
|
|
16
|
-
return {
|
|
17
|
-
isAuthenticated: true,
|
|
18
|
-
session: {
|
|
19
|
-
userId: payload.sub,
|
|
20
|
-
email: payload.email ?? null,
|
|
21
|
-
name: payload.name ?? null,
|
|
22
|
-
token
|
|
23
|
-
}
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
} catch {
|
|
27
|
-
}
|
|
28
|
-
return {
|
|
29
|
-
isAuthenticated: true,
|
|
30
|
-
session: {
|
|
31
|
-
userId: "unknown",
|
|
32
|
-
email: null,
|
|
33
|
-
name: null,
|
|
34
|
-
token
|
|
35
|
-
}
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export {
|
|
40
|
-
auth
|
|
41
|
-
};
|
package/dist/chunk-OCZMYSFX.js
DELETED