@go-mondo/nextjs-auth 0.1.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.
@@ -0,0 +1,146 @@
1
+ import { QueryKey, UseQueryOptions, UseQueryResult } from '@tanstack/react-query';
2
+ import { G as GetAccessTokenOptions, A as AccessTokenResult } from './access-token-UIlXwi3X.js';
3
+ import { C as Claims, U as UserProfile } from './types-CbrOw4QQ.js';
4
+
5
+ /**
6
+ * Options for fetching an access token from browser code.
7
+ */
8
+ type FetchAccessTokenOptions = GetAccessTokenOptions & {
9
+ /**
10
+ * Mounted access-token route. Defaults to `/auth/access-token`.
11
+ */
12
+ route?: string;
13
+ /**
14
+ * Additional fetch options merged with `credentials: "same-origin"`.
15
+ */
16
+ request?: RequestInit;
17
+ };
18
+ /**
19
+ * Access-token provider for imperative browser API clients.
20
+ */
21
+ type AccessTokenProvider = {
22
+ /**
23
+ * Returns a cached access token when it is still fresh, otherwise calls the
24
+ * mounted access-token route and stores the result.
25
+ */
26
+ getAccessToken: (options?: FetchAccessTokenOptions) => Promise<AccessTokenResult>;
27
+ /**
28
+ * Clears the cached entry for the provided route and scopes. Clears every
29
+ * cached entry when no options are provided.
30
+ */
31
+ clear: (options?: Pick<FetchAccessTokenOptions, 'route' | 'scopes'>) => void;
32
+ /**
33
+ * Clears every cached entry.
34
+ */
35
+ clearAll: () => void;
36
+ };
37
+ /**
38
+ * Default TanStack Query key used by {@link useAccessToken}.
39
+ */
40
+ type UseAccessTokenQueryKey = readonly [
41
+ 'mondo-auth',
42
+ 'access-token',
43
+ string,
44
+ string | undefined,
45
+ boolean | undefined,
46
+ number | undefined
47
+ ];
48
+ /**
49
+ * Options accepted by {@link useAccessToken}.
50
+ *
51
+ * All normal TanStack Query options are supported except `queryFn` and
52
+ * `queryKey`, which are owned by the hook. Pass `queryKey` here to override the
53
+ * default key.
54
+ */
55
+ type UseAccessTokenOptions<TData = AccessTokenResult, TQueryKey extends QueryKey = UseAccessTokenQueryKey> = FetchAccessTokenOptions & Omit<UseQueryOptions<AccessTokenResult, Error, TData, TQueryKey>, 'queryFn' | 'queryKey'> & {
56
+ /**
57
+ * TanStack Query key. Defaults to route, scopes, refresh, and refresh skew.
58
+ */
59
+ queryKey?: TQueryKey;
60
+ };
61
+ /**
62
+ * Fetches a current access token from the mounted access-token route.
63
+ *
64
+ * The server reads the local sealed session and refreshes the token when
65
+ * needed, when `refresh` is true, or when requested scopes are missing.
66
+ */
67
+ declare function fetchAccessToken(options?: FetchAccessTokenOptions): Promise<AccessTokenResult>;
68
+ /**
69
+ * Creates an in-memory access-token cache for imperative browser API clients.
70
+ *
71
+ * This is useful when an app needs to make multiple browser-side API calls with
72
+ * a bearer token. Cached tokens are kept only in memory, are refreshed before
73
+ * expiry, and concurrent misses share one `/auth/access-token` request.
74
+ */
75
+ declare function createAccessTokenProvider(defaultOptions?: FetchAccessTokenOptions): AccessTokenProvider;
76
+ /**
77
+ * TanStack Query hook for a current access token.
78
+ *
79
+ * Apps must provide a `QueryClientProvider` above this hook. The hook calls the
80
+ * app's local access-token endpoint; refresh tokens never leave the server.
81
+ */
82
+ declare function useAccessToken<TData = AccessTokenResult, TQueryKey extends QueryKey = UseAccessTokenQueryKey>(options?: UseAccessTokenOptions<TData, TQueryKey>): UseQueryResult<TData, Error>;
83
+
84
+ /**
85
+ * JSON shapes accepted from the mounted session route.
86
+ *
87
+ * The default session route returns `{ user }` as part of the full session, but
88
+ * apps may transform that route to return the user profile directly.
89
+ */
90
+ type UserProfileEndpointResponse<UserClaims extends Claims = Claims> = {
91
+ user?: UserProfile<UserClaims> | null | undefined;
92
+ } | UserProfile<UserClaims> | null | undefined;
93
+ /**
94
+ * Options for fetching the current user profile from browser code.
95
+ */
96
+ type FetchUserProfileOptions = {
97
+ /**
98
+ * Mounted session route. Defaults to `/auth/session`.
99
+ */
100
+ route?: string;
101
+ /**
102
+ * Additional fetch options merged with `credentials: "same-origin"`.
103
+ */
104
+ request?: RequestInit;
105
+ };
106
+ /**
107
+ * Default TanStack Query key used by {@link useUserProfile}.
108
+ */
109
+ type UseUserProfileQueryKey = readonly [
110
+ 'mondo-auth',
111
+ 'user-profile',
112
+ string
113
+ ];
114
+ /**
115
+ * Options accepted by {@link useUserProfile}.
116
+ *
117
+ * All normal TanStack Query options are supported except `queryFn` and
118
+ * `queryKey`, which are owned by the hook. Pass `queryKey` here to override the
119
+ * default key.
120
+ */
121
+ type UseUserProfileOptions<UserClaims extends Claims = Claims, TData = UserProfile<UserClaims> | undefined, TQueryKey extends QueryKey = UseUserProfileQueryKey> = FetchUserProfileOptions & Omit<UseQueryOptions<UserProfile<UserClaims> | undefined, Error, TData, TQueryKey>, 'queryFn' | 'queryKey'> & {
122
+ /**
123
+ * TanStack Query key. Defaults to `["mondo-auth", "user-profile", route]`.
124
+ */
125
+ queryKey?: TQueryKey;
126
+ };
127
+ /**
128
+ * Fetches the currently authenticated user profile from the mounted session
129
+ * route.
130
+ *
131
+ * This reads the app's local server-managed session. It does not call the
132
+ * authorization server directly.
133
+ *
134
+ * @returns The current user profile, or `undefined` when no session exists.
135
+ */
136
+ declare function fetchUserProfile<UserClaims extends Claims = Claims>(options?: FetchUserProfileOptions): Promise<UserProfile<UserClaims> | undefined>;
137
+ /**
138
+ * TanStack Query hook for the currently authenticated user profile.
139
+ *
140
+ * Apps must provide a `QueryClientProvider` above this hook. The hook reads the
141
+ * app's local session endpoint and caches the resulting user profile in
142
+ * TanStack Query.
143
+ */
144
+ declare function useUserProfile<UserClaims extends Claims = Claims, TData = UserProfile<UserClaims> | undefined, TQueryKey extends QueryKey = UseUserProfileQueryKey>(options?: UseUserProfileOptions<UserClaims, TData, TQueryKey>): UseQueryResult<TData, Error>;
145
+
146
+ export { type AccessTokenProvider, type FetchAccessTokenOptions, type FetchUserProfileOptions, type UseAccessTokenOptions, type UseAccessTokenQueryKey, type UseUserProfileOptions, type UseUserProfileQueryKey, type UserProfileEndpointResponse, createAccessTokenProvider, fetchAccessToken, fetchUserProfile, useAccessToken, useUserProfile };
package/dist/hooks.js ADDED
@@ -0,0 +1,230 @@
1
+ 'use client';
2
+ import { useQuery } from '@tanstack/react-query';
3
+
4
+ // src/hooks/access-token.ts
5
+
6
+ // src/config/routes.ts
7
+ var DEFAULT_ROUTES = {
8
+ session: "/auth/session",
9
+ accessToken: "/auth/access-token"};
10
+ function getPublicSessionRoute() {
11
+ return typeof process === "undefined" ? DEFAULT_ROUTES.session : process.env.NEXT_PUBLIC_SESSION_ROUTE || DEFAULT_ROUTES.session;
12
+ }
13
+ function getPublicAccessTokenRoute() {
14
+ return typeof process === "undefined" ? DEFAULT_ROUTES.accessToken : process.env.NEXT_PUBLIC_ACCESS_TOKEN_ROUTE || DEFAULT_ROUTES.accessToken;
15
+ }
16
+
17
+ // src/hooks/access-token.ts
18
+ async function fetchAccessToken(options = {}) {
19
+ const {
20
+ refresh,
21
+ refreshBeforeExpiresIn,
22
+ request,
23
+ route = getPublicAccessTokenRoute(),
24
+ scopes
25
+ } = options;
26
+ const body = getRequestBody({ refresh, refreshBeforeExpiresIn, scopes });
27
+ const headers = new Headers(request?.headers);
28
+ if (body && !headers.has("content-type")) {
29
+ headers.set("content-type", "application/json");
30
+ }
31
+ const response = await fetch(route, {
32
+ ...request,
33
+ body: body ? JSON.stringify(body) : request?.body,
34
+ credentials: "same-origin",
35
+ headers,
36
+ method: body ? "POST" : request?.method
37
+ });
38
+ if (!response.ok) {
39
+ const message = await getErrorMessage(response);
40
+ throw new Error(message);
41
+ }
42
+ return await response.json();
43
+ }
44
+ function createAccessTokenProvider(defaultOptions = {}) {
45
+ const entries = /* @__PURE__ */ new Map();
46
+ const loadAccessToken = async (options) => {
47
+ const key = getAccessTokenCacheKey(options);
48
+ let promise;
49
+ promise = fetchAccessToken(options).then(
50
+ (token) => {
51
+ entries.set(key, { token });
52
+ return token;
53
+ },
54
+ (error) => {
55
+ if (entries.get(key)?.promise === promise) {
56
+ entries.delete(key);
57
+ }
58
+ throw error;
59
+ }
60
+ );
61
+ entries.set(key, { promise });
62
+ return promise;
63
+ };
64
+ return {
65
+ getAccessToken(options = {}) {
66
+ const mergedOptions = mergeFetchAccessTokenOptions(
67
+ defaultOptions,
68
+ options
69
+ );
70
+ const key = getAccessTokenCacheKey(mergedOptions);
71
+ const entry = entries.get(key);
72
+ if (mergedOptions.refresh !== true && entry?.token && canUseCachedAccessToken(entry.token, mergedOptions)) {
73
+ return Promise.resolve(entry.token);
74
+ }
75
+ if (mergedOptions.refresh !== true && entry?.promise) {
76
+ return entry.promise;
77
+ }
78
+ return loadAccessToken(mergedOptions);
79
+ },
80
+ clear(options) {
81
+ if (!options) {
82
+ entries.clear();
83
+ return;
84
+ }
85
+ entries.delete(
86
+ getAccessTokenCacheKey(
87
+ mergeFetchAccessTokenOptions(defaultOptions, options)
88
+ )
89
+ );
90
+ },
91
+ clearAll() {
92
+ entries.clear();
93
+ }
94
+ };
95
+ }
96
+ function useAccessToken(options = {}) {
97
+ const {
98
+ queryKey,
99
+ refresh,
100
+ refreshBeforeExpiresIn,
101
+ request,
102
+ route = getPublicAccessTokenRoute(),
103
+ scopes,
104
+ ...queryOptions
105
+ } = options;
106
+ return useQuery({
107
+ queryKey: queryKey ?? [
108
+ "mondo-auth",
109
+ "access-token",
110
+ route,
111
+ normalizeScopes(scopes),
112
+ refresh,
113
+ refreshBeforeExpiresIn
114
+ ],
115
+ queryFn: () => fetchAccessToken({
116
+ refresh,
117
+ refreshBeforeExpiresIn,
118
+ request,
119
+ route,
120
+ scopes
121
+ }),
122
+ ...queryOptions
123
+ });
124
+ }
125
+ function getRequestBody(options) {
126
+ if (options.refresh === void 0 && options.refreshBeforeExpiresIn === void 0 && options.scopes === void 0) {
127
+ return void 0;
128
+ }
129
+ return options;
130
+ }
131
+ function mergeFetchAccessTokenOptions(defaultOptions, options) {
132
+ const request = mergeRequestInit(defaultOptions.request, options.request);
133
+ return {
134
+ ...defaultOptions,
135
+ ...options,
136
+ request
137
+ };
138
+ }
139
+ function mergeRequestInit(defaultRequest, request) {
140
+ if (!defaultRequest && !request) {
141
+ return void 0;
142
+ }
143
+ return {
144
+ ...defaultRequest,
145
+ ...request,
146
+ headers: mergeHeaders(defaultRequest?.headers, request?.headers)
147
+ };
148
+ }
149
+ function mergeHeaders(defaultHeaders, headers) {
150
+ if (!defaultHeaders && !headers) {
151
+ return void 0;
152
+ }
153
+ const mergedHeaders = new Headers(defaultHeaders);
154
+ new Headers(headers).forEach((value, key) => {
155
+ mergedHeaders.set(key, value);
156
+ });
157
+ return mergedHeaders;
158
+ }
159
+ function canUseCachedAccessToken(token, options) {
160
+ const skew = options.refreshBeforeExpiresIn ?? 60;
161
+ return token.expiresAt > Math.floor(Date.now() / 1e3) + skew;
162
+ }
163
+ function getAccessTokenCacheKey(options) {
164
+ return JSON.stringify([
165
+ options.route ?? getPublicAccessTokenRoute(),
166
+ normalizeScopes(options.scopes)
167
+ ]);
168
+ }
169
+ async function getErrorMessage(response) {
170
+ const payload = await readJson(response);
171
+ if (typeof payload?.error_description === "string") {
172
+ return payload.error_description;
173
+ }
174
+ return `Unable to load an access token (${response.status}).`;
175
+ }
176
+ async function readJson(response) {
177
+ try {
178
+ return await response.json();
179
+ } catch {
180
+ return void 0;
181
+ }
182
+ }
183
+ function normalizeScopes(scopes) {
184
+ return Array.isArray(scopes) ? scopes.join(" ") : scopes;
185
+ }
186
+ async function fetchUserProfile(options = {}) {
187
+ const { request, route = getPublicSessionRoute() } = options;
188
+ const response = await fetch(route, {
189
+ credentials: "same-origin",
190
+ ...request
191
+ });
192
+ if (response.status === 401 || response.status === 403) {
193
+ return void 0;
194
+ }
195
+ if (!response.ok) {
196
+ throw new Error(`Unable to load the current user (${response.status}).`);
197
+ }
198
+ return userProfileFromResponse(
199
+ await response.json()
200
+ );
201
+ }
202
+ function useUserProfile(options = {}) {
203
+ const {
204
+ queryKey,
205
+ route = getPublicSessionRoute(),
206
+ request,
207
+ ...queryOptions
208
+ } = options;
209
+ return useQuery({
210
+ queryKey: queryKey ?? ["mondo-auth", "user-profile", route],
211
+ queryFn: () => fetchUserProfile({ request, route }),
212
+ ...queryOptions
213
+ });
214
+ }
215
+ function userProfileFromResponse(payload) {
216
+ if (!payload || typeof payload !== "object") {
217
+ return void 0;
218
+ }
219
+ if ("user" in payload) {
220
+ return isRecord(payload.user) ? payload.user : void 0;
221
+ }
222
+ return payload;
223
+ }
224
+ function isRecord(value) {
225
+ return Boolean(value && typeof value === "object");
226
+ }
227
+
228
+ export { createAccessTokenProvider, fetchAccessToken, fetchUserProfile, useAccessToken, useUserProfile };
229
+ //# sourceMappingURL=hooks.js.map
230
+ //# sourceMappingURL=hooks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/config/routes.ts","../src/hooks/access-token.ts","../src/hooks/user.ts"],"names":["useQuery"],"mappings":"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","file":"hooks.js","sourcesContent":["/**\n * Built-in auth route defaults used by both server configuration and\n * browser-safe client helpers.\n */\nexport const DEFAULT_ROUTES = {\n login: '/auth/login',\n callback: '/auth/callback',\n logout: '/auth/logout',\n session: '/auth/session',\n accessToken: '/auth/access-token',\n postLogoutRedirect: '/',\n} as const;\n\n/**\n * Returns the session route that browser code can safely call.\n *\n * The full server configuration reads secret-bearing environment variables, so\n * client hooks use the public session route override instead.\n */\nexport function getPublicSessionRoute(): string {\n return typeof process === 'undefined'\n ? DEFAULT_ROUTES.session\n : process.env.NEXT_PUBLIC_SESSION_ROUTE || DEFAULT_ROUTES.session;\n}\n\n/**\n * Returns the access-token route that browser code can safely call.\n *\n * The full server configuration may read secret-bearing environment variables,\n * so client hooks use the public access-token route override instead.\n */\nexport function getPublicAccessTokenRoute(): string {\n return typeof process === 'undefined'\n ? DEFAULT_ROUTES.accessToken\n : process.env.NEXT_PUBLIC_ACCESS_TOKEN_ROUTE || DEFAULT_ROUTES.accessToken;\n}\n","import {\n useQuery,\n type QueryKey,\n type UseQueryOptions,\n type UseQueryResult,\n} from '@tanstack/react-query';\nimport { getPublicAccessTokenRoute } from '../config/routes';\nimport type {\n AccessTokenResult,\n GetAccessTokenOptions,\n} from '../oauth/access-token';\n\n/**\n * Options for fetching an access token from browser code.\n */\nexport type FetchAccessTokenOptions = GetAccessTokenOptions & {\n /**\n * Mounted access-token route. Defaults to `/auth/access-token`.\n */\n route?: string;\n\n /**\n * Additional fetch options merged with `credentials: \"same-origin\"`.\n */\n request?: RequestInit;\n};\n\n/**\n * Access-token provider for imperative browser API clients.\n */\nexport type AccessTokenProvider = {\n /**\n * Returns a cached access token when it is still fresh, otherwise calls the\n * mounted access-token route and stores the result.\n */\n getAccessToken: (\n options?: FetchAccessTokenOptions,\n ) => Promise<AccessTokenResult>;\n\n /**\n * Clears the cached entry for the provided route and scopes. Clears every\n * cached entry when no options are provided.\n */\n clear: (options?: Pick<FetchAccessTokenOptions, 'route' | 'scopes'>) => void;\n\n /**\n * Clears every cached entry.\n */\n clearAll: () => void;\n};\n\n/**\n * Default TanStack Query key used by {@link useAccessToken}.\n */\nexport type UseAccessTokenQueryKey = readonly [\n 'mondo-auth',\n 'access-token',\n string,\n string | undefined,\n boolean | undefined,\n number | undefined,\n];\n\n/**\n * Options accepted by {@link useAccessToken}.\n *\n * All normal TanStack Query options are supported except `queryFn` and\n * `queryKey`, which are owned by the hook. Pass `queryKey` here to override the\n * default key.\n */\nexport type UseAccessTokenOptions<\n TData = AccessTokenResult,\n TQueryKey extends QueryKey = UseAccessTokenQueryKey,\n> = FetchAccessTokenOptions &\n Omit<\n UseQueryOptions<AccessTokenResult, Error, TData, TQueryKey>,\n 'queryFn' | 'queryKey'\n > & {\n /**\n * TanStack Query key. Defaults to route, scopes, refresh, and refresh skew.\n */\n queryKey?: TQueryKey;\n };\n\n/**\n * Fetches a current access token from the mounted access-token route.\n *\n * The server reads the local sealed session and refreshes the token when\n * needed, when `refresh` is true, or when requested scopes are missing.\n */\nexport async function fetchAccessToken(\n options: FetchAccessTokenOptions = {},\n): Promise<AccessTokenResult> {\n const {\n refresh,\n refreshBeforeExpiresIn,\n request,\n route = getPublicAccessTokenRoute(),\n scopes,\n } = options;\n const body = getRequestBody({ refresh, refreshBeforeExpiresIn, scopes });\n const headers = new Headers(request?.headers);\n\n if (body && !headers.has('content-type')) {\n headers.set('content-type', 'application/json');\n }\n\n const response = await fetch(route, {\n ...request,\n body: body ? JSON.stringify(body) : request?.body,\n credentials: 'same-origin',\n headers,\n method: body ? 'POST' : request?.method,\n });\n\n if (!response.ok) {\n const message = await getErrorMessage(response);\n throw new Error(message);\n }\n\n return (await response.json()) as AccessTokenResult;\n}\n\n/**\n * Creates an in-memory access-token cache for imperative browser API clients.\n *\n * This is useful when an app needs to make multiple browser-side API calls with\n * a bearer token. Cached tokens are kept only in memory, are refreshed before\n * expiry, and concurrent misses share one `/auth/access-token` request.\n */\nexport function createAccessTokenProvider(\n defaultOptions: FetchAccessTokenOptions = {},\n): AccessTokenProvider {\n const entries = new Map<string, AccessTokenCacheEntry>();\n\n const loadAccessToken = async (options: FetchAccessTokenOptions) => {\n const key = getAccessTokenCacheKey(options);\n let promise: Promise<AccessTokenResult>;\n\n promise = fetchAccessToken(options).then(\n (token) => {\n entries.set(key, { token });\n return token;\n },\n (error) => {\n if (entries.get(key)?.promise === promise) {\n entries.delete(key);\n }\n\n throw error;\n },\n );\n\n entries.set(key, { promise });\n return promise;\n };\n\n return {\n getAccessToken(options = {}) {\n const mergedOptions = mergeFetchAccessTokenOptions(\n defaultOptions,\n options,\n );\n const key = getAccessTokenCacheKey(mergedOptions);\n const entry = entries.get(key);\n\n if (\n mergedOptions.refresh !== true &&\n entry?.token &&\n canUseCachedAccessToken(entry.token, mergedOptions)\n ) {\n return Promise.resolve(entry.token);\n }\n\n if (mergedOptions.refresh !== true && entry?.promise) {\n return entry.promise;\n }\n\n return loadAccessToken(mergedOptions);\n },\n\n clear(options) {\n if (!options) {\n entries.clear();\n return;\n }\n\n entries.delete(\n getAccessTokenCacheKey(\n mergeFetchAccessTokenOptions(defaultOptions, options),\n ),\n );\n },\n\n clearAll() {\n entries.clear();\n },\n };\n}\n\n/**\n * TanStack Query hook for a current access token.\n *\n * Apps must provide a `QueryClientProvider` above this hook. The hook calls the\n * app's local access-token endpoint; refresh tokens never leave the server.\n */\nexport function useAccessToken<\n TData = AccessTokenResult,\n TQueryKey extends QueryKey = UseAccessTokenQueryKey,\n>(\n options: UseAccessTokenOptions<TData, TQueryKey> = {},\n): UseQueryResult<TData, Error> {\n const {\n queryKey,\n refresh,\n refreshBeforeExpiresIn,\n request,\n route = getPublicAccessTokenRoute(),\n scopes,\n ...queryOptions\n } = options;\n\n return useQuery({\n queryKey:\n queryKey ??\n ([\n 'mondo-auth',\n 'access-token',\n route,\n normalizeScopes(scopes),\n refresh,\n refreshBeforeExpiresIn,\n ] satisfies UseAccessTokenQueryKey),\n queryFn: () =>\n fetchAccessToken({\n refresh,\n refreshBeforeExpiresIn,\n request,\n route,\n scopes,\n }),\n ...queryOptions,\n } as UseQueryOptions<AccessTokenResult, Error, TData, TQueryKey>);\n}\n\nfunction getRequestBody(options: GetAccessTokenOptions) {\n if (\n options.refresh === undefined &&\n options.refreshBeforeExpiresIn === undefined &&\n options.scopes === undefined\n ) {\n return undefined;\n }\n\n return options;\n}\n\ntype AccessTokenCacheEntry = {\n token?: AccessTokenResult;\n promise?: Promise<AccessTokenResult>;\n};\n\nfunction mergeFetchAccessTokenOptions(\n defaultOptions: FetchAccessTokenOptions,\n options: FetchAccessTokenOptions,\n): FetchAccessTokenOptions {\n const request = mergeRequestInit(defaultOptions.request, options.request);\n\n return {\n ...defaultOptions,\n ...options,\n request,\n };\n}\n\nfunction mergeRequestInit(\n defaultRequest: RequestInit | undefined,\n request: RequestInit | undefined,\n): RequestInit | undefined {\n if (!defaultRequest && !request) {\n return undefined;\n }\n\n return {\n ...defaultRequest,\n ...request,\n headers: mergeHeaders(defaultRequest?.headers, request?.headers),\n };\n}\n\nfunction mergeHeaders(\n defaultHeaders: HeadersInit | undefined,\n headers: HeadersInit | undefined,\n): Headers | undefined {\n if (!defaultHeaders && !headers) {\n return undefined;\n }\n\n const mergedHeaders = new Headers(defaultHeaders);\n new Headers(headers).forEach((value, key) => {\n mergedHeaders.set(key, value);\n });\n\n return mergedHeaders;\n}\n\nfunction canUseCachedAccessToken(\n token: AccessTokenResult,\n options: GetAccessTokenOptions,\n): boolean {\n const skew = options.refreshBeforeExpiresIn ?? 60;\n return token.expiresAt > Math.floor(Date.now() / 1000) + skew;\n}\n\nfunction getAccessTokenCacheKey(\n options: Pick<FetchAccessTokenOptions, 'route' | 'scopes'>,\n) {\n return JSON.stringify([\n options.route ?? getPublicAccessTokenRoute(),\n normalizeScopes(options.scopes),\n ]);\n}\n\nasync function getErrorMessage(response: Response): Promise<string> {\n const payload = (await readJson(response)) as\n | { error_description?: unknown }\n | undefined;\n\n if (typeof payload?.error_description === 'string') {\n return payload.error_description;\n }\n\n return `Unable to load an access token (${response.status}).`;\n}\n\nasync function readJson(response: Response): Promise<unknown> {\n try {\n return await response.json();\n } catch {\n return undefined;\n }\n}\n\nfunction normalizeScopes(scopes: string | Array<string> | undefined) {\n return Array.isArray(scopes) ? scopes.join(' ') : scopes;\n}\n","import {\n useQuery,\n type QueryKey,\n type UseQueryOptions,\n type UseQueryResult,\n} from '@tanstack/react-query';\nimport { getPublicSessionRoute } from '../config/routes';\nimport type { Claims, UserProfile } from '../session/types';\n\n/**\n * JSON shapes accepted from the mounted session route.\n *\n * The default session route returns `{ user }` as part of the full session, but\n * apps may transform that route to return the user profile directly.\n */\nexport type UserProfileEndpointResponse<UserClaims extends Claims = Claims> =\n | { user?: UserProfile<UserClaims> | null | undefined }\n | UserProfile<UserClaims>\n | null\n | undefined;\n\n/**\n * Options for fetching the current user profile from browser code.\n */\nexport type FetchUserProfileOptions = {\n /**\n * Mounted session route. Defaults to `/auth/session`.\n */\n route?: string;\n\n /**\n * Additional fetch options merged with `credentials: \"same-origin\"`.\n */\n request?: RequestInit;\n};\n\n/**\n * Default TanStack Query key used by {@link useUserProfile}.\n */\nexport type UseUserProfileQueryKey = readonly [\n 'mondo-auth',\n 'user-profile',\n string,\n];\n\n/**\n * Options accepted by {@link useUserProfile}.\n *\n * All normal TanStack Query options are supported except `queryFn` and\n * `queryKey`, which are owned by the hook. Pass `queryKey` here to override the\n * default key.\n */\nexport type UseUserProfileOptions<\n UserClaims extends Claims = Claims,\n TData = UserProfile<UserClaims> | undefined,\n TQueryKey extends QueryKey = UseUserProfileQueryKey,\n> = FetchUserProfileOptions &\n Omit<\n UseQueryOptions<\n UserProfile<UserClaims> | undefined,\n Error,\n TData,\n TQueryKey\n >,\n 'queryFn' | 'queryKey'\n > & {\n /**\n * TanStack Query key. Defaults to `[\"mondo-auth\", \"user-profile\", route]`.\n */\n queryKey?: TQueryKey;\n };\n\n/**\n * Fetches the currently authenticated user profile from the mounted session\n * route.\n *\n * This reads the app's local server-managed session. It does not call the\n * authorization server directly.\n *\n * @returns The current user profile, or `undefined` when no session exists.\n */\nexport async function fetchUserProfile<UserClaims extends Claims = Claims>(\n options: FetchUserProfileOptions = {},\n): Promise<UserProfile<UserClaims> | undefined> {\n const { request, route = getPublicSessionRoute() } = options;\n const response = await fetch(route, {\n credentials: 'same-origin',\n ...request,\n });\n\n if (response.status === 401 || response.status === 403) {\n return undefined;\n }\n\n if (!response.ok) {\n throw new Error(`Unable to load the current user (${response.status}).`);\n }\n\n return userProfileFromResponse<UserClaims>(\n (await response.json()) as UserProfileEndpointResponse<UserClaims>,\n );\n}\n\n/**\n * TanStack Query hook for the currently authenticated user profile.\n *\n * Apps must provide a `QueryClientProvider` above this hook. The hook reads the\n * app's local session endpoint and caches the resulting user profile in\n * TanStack Query.\n */\nexport function useUserProfile<\n UserClaims extends Claims = Claims,\n TData = UserProfile<UserClaims> | undefined,\n TQueryKey extends QueryKey = UseUserProfileQueryKey,\n>(\n options: UseUserProfileOptions<UserClaims, TData, TQueryKey> = {},\n): UseQueryResult<TData, Error> {\n const {\n queryKey,\n route = getPublicSessionRoute(),\n request,\n ...queryOptions\n } = options;\n\n return useQuery({\n queryKey:\n queryKey ??\n (['mondo-auth', 'user-profile', route] satisfies UseUserProfileQueryKey),\n queryFn: () => fetchUserProfile<UserClaims>({ request, route }),\n ...queryOptions,\n } as UseQueryOptions<\n UserProfile<UserClaims> | undefined,\n Error,\n TData,\n TQueryKey\n >);\n}\n\nfunction userProfileFromResponse<UserClaims extends Claims>(\n payload: UserProfileEndpointResponse<UserClaims>,\n): UserProfile<UserClaims> | undefined {\n if (!payload || typeof payload !== 'object') {\n return undefined;\n }\n\n if ('user' in payload) {\n return isRecord(payload.user)\n ? (payload.user as UserProfile<UserClaims>)\n : undefined;\n }\n\n return payload as UserProfile<UserClaims>;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return Boolean(value && typeof value === 'object');\n}\n"]}
package/dist/oauth.cjs ADDED
@@ -0,0 +1,10 @@
1
+ 'use strict';
2
+
3
+ // src/oauth/types.ts
4
+ var CodeChallengeMethod = {
5
+ S256: "S256"
6
+ };
7
+
8
+ exports.CodeChallengeMethod = CodeChallengeMethod;
9
+ //# sourceMappingURL=oauth.cjs.map
10
+ //# sourceMappingURL=oauth.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/oauth/types.ts"],"names":[],"mappings":";;;AAgBO,IAAM,mBAAA,GAAsB;AAAA,EACjC,IAAA,EAAM;AACR","file":"oauth.cjs","sourcesContent":["/**\n * Minimal token endpoint response shape used by the session model.\n */\nexport interface TokenEndpointResponse {\n access_token?: string;\n token_type?: string;\n id_token?: string;\n refresh_token?: string;\n scope?: string;\n expires_in?: number;\n [key: string]: unknown;\n}\n\n/**\n * PKCE challenge method supported by this SDK.\n */\nexport const CodeChallengeMethod = {\n S256: 'S256',\n} as const;\n\ntype PKCEParams = {\n code_challenge_method: typeof CodeChallengeMethod.S256;\n code_challenge: string;\n};\n\ntype AuthorizationCodeOptionalParams = {\n audience?: string;\n};\n\n/**\n * Authorization URL parameters assembled for the login redirect.\n *\n * Runtime-generated fields such as `state`, `nonce`, and PKCE values are added\n * by the login route handler rather than accepted from user config.\n */\nexport type AuthorizationCodeParams = {\n response_type: 'code';\n scope: string;\n redirect_uri: string;\n state: string;\n nonce: string;\n response_mode?: 'query' | 'form_post';\n display?: 'page' | 'popup' | 'touch' | 'wap';\n prompt?: 'none' | 'login' | 'consent' | 'select_account';\n max_age?: number;\n ui_locales?: string;\n id_token_hint?: string;\n login_hint?: string;\n acr_values?: string;\n} & AuthorizationCodeOptionalParams &\n PKCEParams;\n\ntype BaseConfigurableAuthorizationParams = Omit<\n AuthorizationCodeParams,\n 'client_id' | 'state' | 'nonce' | 'code_challenge_method' | 'code_challenge'\n>;\n\n/**\n * Per-request authorization parameter overrides accepted by `handleLogin`.\n */\nexport type OverrideAuthorizationParams =\n Partial<BaseConfigurableAuthorizationParams>;\n"]}
@@ -0,0 +1,55 @@
1
+ export { A as AccessTokenResult, a as GetAccessToken, G as GetAccessTokenOptions } from './access-token-UIlXwi3X.cjs';
2
+
3
+ /**
4
+ * Minimal token endpoint response shape used by the session model.
5
+ */
6
+ interface TokenEndpointResponse {
7
+ access_token?: string;
8
+ token_type?: string;
9
+ id_token?: string;
10
+ refresh_token?: string;
11
+ scope?: string;
12
+ expires_in?: number;
13
+ [key: string]: unknown;
14
+ }
15
+ /**
16
+ * PKCE challenge method supported by this SDK.
17
+ */
18
+ declare const CodeChallengeMethod: {
19
+ readonly S256: "S256";
20
+ };
21
+ type PKCEParams = {
22
+ code_challenge_method: typeof CodeChallengeMethod.S256;
23
+ code_challenge: string;
24
+ };
25
+ type AuthorizationCodeOptionalParams = {
26
+ audience?: string;
27
+ };
28
+ /**
29
+ * Authorization URL parameters assembled for the login redirect.
30
+ *
31
+ * Runtime-generated fields such as `state`, `nonce`, and PKCE values are added
32
+ * by the login route handler rather than accepted from user config.
33
+ */
34
+ type AuthorizationCodeParams = {
35
+ response_type: 'code';
36
+ scope: string;
37
+ redirect_uri: string;
38
+ state: string;
39
+ nonce: string;
40
+ response_mode?: 'query' | 'form_post';
41
+ display?: 'page' | 'popup' | 'touch' | 'wap';
42
+ prompt?: 'none' | 'login' | 'consent' | 'select_account';
43
+ max_age?: number;
44
+ ui_locales?: string;
45
+ id_token_hint?: string;
46
+ login_hint?: string;
47
+ acr_values?: string;
48
+ } & AuthorizationCodeOptionalParams & PKCEParams;
49
+ type BaseConfigurableAuthorizationParams = Omit<AuthorizationCodeParams, 'client_id' | 'state' | 'nonce' | 'code_challenge_method' | 'code_challenge'>;
50
+ /**
51
+ * Per-request authorization parameter overrides accepted by `handleLogin`.
52
+ */
53
+ type OverrideAuthorizationParams = Partial<BaseConfigurableAuthorizationParams>;
54
+
55
+ export { type AuthorizationCodeParams, CodeChallengeMethod, type OverrideAuthorizationParams, type TokenEndpointResponse };
@@ -0,0 +1,55 @@
1
+ export { A as AccessTokenResult, a as GetAccessToken, G as GetAccessTokenOptions } from './access-token-UIlXwi3X.js';
2
+
3
+ /**
4
+ * Minimal token endpoint response shape used by the session model.
5
+ */
6
+ interface TokenEndpointResponse {
7
+ access_token?: string;
8
+ token_type?: string;
9
+ id_token?: string;
10
+ refresh_token?: string;
11
+ scope?: string;
12
+ expires_in?: number;
13
+ [key: string]: unknown;
14
+ }
15
+ /**
16
+ * PKCE challenge method supported by this SDK.
17
+ */
18
+ declare const CodeChallengeMethod: {
19
+ readonly S256: "S256";
20
+ };
21
+ type PKCEParams = {
22
+ code_challenge_method: typeof CodeChallengeMethod.S256;
23
+ code_challenge: string;
24
+ };
25
+ type AuthorizationCodeOptionalParams = {
26
+ audience?: string;
27
+ };
28
+ /**
29
+ * Authorization URL parameters assembled for the login redirect.
30
+ *
31
+ * Runtime-generated fields such as `state`, `nonce`, and PKCE values are added
32
+ * by the login route handler rather than accepted from user config.
33
+ */
34
+ type AuthorizationCodeParams = {
35
+ response_type: 'code';
36
+ scope: string;
37
+ redirect_uri: string;
38
+ state: string;
39
+ nonce: string;
40
+ response_mode?: 'query' | 'form_post';
41
+ display?: 'page' | 'popup' | 'touch' | 'wap';
42
+ prompt?: 'none' | 'login' | 'consent' | 'select_account';
43
+ max_age?: number;
44
+ ui_locales?: string;
45
+ id_token_hint?: string;
46
+ login_hint?: string;
47
+ acr_values?: string;
48
+ } & AuthorizationCodeOptionalParams & PKCEParams;
49
+ type BaseConfigurableAuthorizationParams = Omit<AuthorizationCodeParams, 'client_id' | 'state' | 'nonce' | 'code_challenge_method' | 'code_challenge'>;
50
+ /**
51
+ * Per-request authorization parameter overrides accepted by `handleLogin`.
52
+ */
53
+ type OverrideAuthorizationParams = Partial<BaseConfigurableAuthorizationParams>;
54
+
55
+ export { type AuthorizationCodeParams, CodeChallengeMethod, type OverrideAuthorizationParams, type TokenEndpointResponse };
package/dist/oauth.js ADDED
@@ -0,0 +1,8 @@
1
+ // src/oauth/types.ts
2
+ var CodeChallengeMethod = {
3
+ S256: "S256"
4
+ };
5
+
6
+ export { CodeChallengeMethod };
7
+ //# sourceMappingURL=oauth.js.map
8
+ //# sourceMappingURL=oauth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/oauth/types.ts"],"names":[],"mappings":";AAgBO,IAAM,mBAAA,GAAsB;AAAA,EACjC,IAAA,EAAM;AACR","file":"oauth.js","sourcesContent":["/**\n * Minimal token endpoint response shape used by the session model.\n */\nexport interface TokenEndpointResponse {\n access_token?: string;\n token_type?: string;\n id_token?: string;\n refresh_token?: string;\n scope?: string;\n expires_in?: number;\n [key: string]: unknown;\n}\n\n/**\n * PKCE challenge method supported by this SDK.\n */\nexport const CodeChallengeMethod = {\n S256: 'S256',\n} as const;\n\ntype PKCEParams = {\n code_challenge_method: typeof CodeChallengeMethod.S256;\n code_challenge: string;\n};\n\ntype AuthorizationCodeOptionalParams = {\n audience?: string;\n};\n\n/**\n * Authorization URL parameters assembled for the login redirect.\n *\n * Runtime-generated fields such as `state`, `nonce`, and PKCE values are added\n * by the login route handler rather than accepted from user config.\n */\nexport type AuthorizationCodeParams = {\n response_type: 'code';\n scope: string;\n redirect_uri: string;\n state: string;\n nonce: string;\n response_mode?: 'query' | 'form_post';\n display?: 'page' | 'popup' | 'touch' | 'wap';\n prompt?: 'none' | 'login' | 'consent' | 'select_account';\n max_age?: number;\n ui_locales?: string;\n id_token_hint?: string;\n login_hint?: string;\n acr_values?: string;\n} & AuthorizationCodeOptionalParams &\n PKCEParams;\n\ntype BaseConfigurableAuthorizationParams = Omit<\n AuthorizationCodeParams,\n 'client_id' | 'state' | 'nonce' | 'code_challenge_method' | 'code_challenge'\n>;\n\n/**\n * Per-request authorization parameter overrides accepted by `handleLogin`.\n */\nexport type OverrideAuthorizationParams =\n Partial<BaseConfigurableAuthorizationParams>;\n"]}
@@ -0,0 +1,45 @@
1
+ 'use strict';
2
+
3
+ // src/session/model.ts
4
+ var Session = class {
5
+ /**
6
+ * The authenticated user (claims from the `id_token`)
7
+ */
8
+ user;
9
+ /**
10
+ * A timestamp when authentication / session occurred
11
+ */
12
+ issuedAt;
13
+ /**
14
+ * A timestamp when authentication / session was last updated (touched)
15
+ */
16
+ updatedAt;
17
+ /**
18
+ * A timestamp when the authentication / session is set to expire
19
+ */
20
+ expiresAt;
21
+ /**
22
+ * OAuth access-token state stored separately from the base session payload.
23
+ */
24
+ authorization;
25
+ /**
26
+ * OIDC authentication token state stored separately from the base session
27
+ * payload.
28
+ */
29
+ authentication;
30
+ /**
31
+ * Creates a normalized session object from sealed cookie payloads.
32
+ */
33
+ constructor(props) {
34
+ this.user = props.user;
35
+ this.issuedAt = props.issuedAt;
36
+ this.updatedAt = props.updatedAt;
37
+ this.expiresAt = props.expiresAt;
38
+ this.authentication = props.authentication;
39
+ this.authorization = props.authorization;
40
+ }
41
+ };
42
+
43
+ exports.Session = Session;
44
+ //# sourceMappingURL=session.cjs.map
45
+ //# sourceMappingURL=session.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/session/model.ts"],"names":[],"mappings":";;;AA2BO,IAAM,UAAN,MAEP;AAAA;AAAA;AAAA;AAAA,EAIE,IAAA;AAAA;AAAA;AAAA;AAAA,EAKA,QAAA;AAAA;AAAA;AAAA;AAAA,EAKA,SAAA;AAAA;AAAA;AAAA;AAAA,EAKA,SAAA;AAAA;AAAA;AAAA;AAAA,EAKA,aAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,cAAA;AAAA;AAAA;AAAA;AAAA,EAOA,YAAY,KAAA,EAAsC;AAChD,IAAA,IAAA,CAAK,OAAO,KAAA,CAAM,IAAA;AAClB,IAAA,IAAA,CAAK,WAAW,KAAA,CAAM,QAAA;AACtB,IAAA,IAAA,CAAK,YAAY,KAAA,CAAM,SAAA;AACvB,IAAA,IAAA,CAAK,YAAY,KAAA,CAAM,SAAA;AACvB,IAAA,IAAA,CAAK,iBAAiB,KAAA,CAAM,cAAA;AAC5B,IAAA,IAAA,CAAK,gBAAgB,KAAA,CAAM,aAAA;AAAA,EAC7B;AACF","file":"session.cjs","sourcesContent":["import type { TokenEndpointResponse } from '../oauth/types';\nimport type {\n Claims,\n IdTokenClaims,\n SessionAuthentication,\n SessionAuthorization,\n SessionInterface,\n UserProfile,\n} from './types';\n\n/**\n * Serializable session payload stored across sealed cookies.\n */\nexport type SerializedSession<UserClaims extends Claims = Claims> =\n SessionInterface<UserClaims> & {\n authentication?: SessionAuthentication;\n authorization?: SessionAuthorization;\n };\n\n/**\n * The user's session.\n *\n * The public session shape combines the base user claims cookie with optional\n * authentication and authorization cookies.\n *\n * @category Server\n */\nexport class Session<UserClaims extends Claims = Claims>\n implements SessionInterface<UserClaims>\n{\n /**\n * The authenticated user (claims from the `id_token`)\n */\n user: UserProfile<UserClaims>;\n\n /**\n * A timestamp when authentication / session occurred\n */\n issuedAt: number;\n\n /**\n * A timestamp when authentication / session was last updated (touched)\n */\n updatedAt: number;\n\n /**\n * A timestamp when the authentication / session is set to expire\n */\n expiresAt: number;\n\n /**\n * OAuth access-token state stored separately from the base session payload.\n */\n authorization?: SessionAuthorization;\n\n /**\n * OIDC authentication token state stored separately from the base session\n * payload.\n */\n authentication?: SessionAuthentication;\n\n [key: string]: any;\n\n /**\n * Creates a normalized session object from sealed cookie payloads.\n */\n constructor(props: SerializedSession<UserClaims>) {\n this.user = props.user;\n this.issuedAt = props.issuedAt;\n this.updatedAt = props.updatedAt;\n this.expiresAt = props.expiresAt;\n this.authentication = props.authentication;\n this.authorization = props.authorization;\n }\n}\n\n/**\n * Converts an OIDC token endpoint response into the session model stored in\n * sealed cookies.\n *\n * @param tokenEndpointResponse - Token endpoint response returned by\n * `openid-client`.\n */\nexport function fromTokenEndpointResponse<UserClaims extends Claims = Claims>(\n tokenEndpointResponse: TokenEndpointResponse,\n): Session<UserClaims> {\n const { iat, exp, aud, iss, nonce, ...user } = decodeJwt<IdTokenClaims>(\n tokenEndpointResponse.id_token as string,\n );\n\n const {\n id_token,\n access_token,\n scope,\n expires_in,\n expires_at,\n refresh_token,\n token_type,\n ...remainder\n } = tokenEndpointResponse;\n\n const authorization = access_token\n ? {\n accessToken: access_token,\n scope,\n expiresAt: Math.floor(Date.now() / 1000) + Number(expires_in),\n refreshToken: refresh_token,\n type: token_type,\n }\n : undefined;\n\n const authentication = id_token\n ? {\n idToken: id_token,\n }\n : undefined;\n\n return Object.assign(\n new Session({\n user: user as UserProfile<UserClaims>,\n issuedAt: iat,\n updatedAt: iat,\n expiresAt: exp,\n authorization,\n authentication,\n }),\n remainder,\n );\n}\n\nfunction decodeJwt<TClaims>(jwt: string): TClaims {\n const [, payload] = jwt.split('.');\n\n if (!payload) {\n throw new TypeError('Invalid JWT payload.');\n }\n\n const normalized = payload.replace(/-/g, '+').replace(/_/g, '/');\n const padded = normalized.padEnd(\n normalized.length + ((4 - (normalized.length % 4)) % 4),\n '=',\n );\n\n const decoded =\n typeof atob === 'function'\n ? atob(padded)\n : Buffer.from(padded, 'base64').toString('binary');\n\n const json = decodeURIComponent(\n Array.from(\n decoded,\n (char) => `%${char.charCodeAt(0).toString(16).padStart(2, '0')}`,\n ).join(''),\n );\n\n return JSON.parse(json) as TClaims;\n}\n"]}