najm-auth 1.1.44 → 2.0.2
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 +765 -664
- package/dist/{NajmAuthClient-D08--i69.d.ts → NajmAuthClient-B9dGk9MH.d.ts} +12 -1
- package/dist/client/index.d.ts +2 -2
- package/dist/client/index.js +50 -1
- package/dist/client/react/index.d.ts +41 -2
- package/dist/client/react/index.js +129 -5
- package/dist/client/server/index.d.ts +1 -1
- package/dist/client/server/index.js +49 -0
- package/dist/index.d.ts +171 -14
- package/dist/index.js +1470 -589
- package/dist/schema/pg.d.ts +222 -1
- package/dist/schema/pg.js +13 -1
- package/dist/schema/sqlite.d.ts +246 -1
- package/dist/schema/sqlite.js +12 -0
- package/package.json +13 -10
|
@@ -77,6 +77,11 @@ interface AuthClientConfig {
|
|
|
77
77
|
/** Request timeout in milliseconds (default: 30000) */
|
|
78
78
|
timeout?: number;
|
|
79
79
|
}
|
|
80
|
+
type OAuthProvider = 'google';
|
|
81
|
+
interface OAuthLoginOptions {
|
|
82
|
+
/** Same-origin frontend path after OAuth completes. */
|
|
83
|
+
returnTo?: string;
|
|
84
|
+
}
|
|
80
85
|
/**
|
|
81
86
|
* Auth event types
|
|
82
87
|
*/
|
|
@@ -182,6 +187,11 @@ declare class NajmAuthClient {
|
|
|
182
187
|
password: string;
|
|
183
188
|
}): Promise<AuthUser>;
|
|
184
189
|
register(data: Record<string, unknown>): Promise<AuthUser>;
|
|
190
|
+
getOAuthLoginUrl(provider: OAuthProvider, options?: OAuthLoginOptions): string;
|
|
191
|
+
loginWithOAuth(provider: OAuthProvider, options?: OAuthLoginOptions): void;
|
|
192
|
+
loginWithGoogle(options?: OAuthLoginOptions): void;
|
|
193
|
+
linkOAuthAccount(provider: OAuthProvider, options?: OAuthLoginOptions): Promise<void>;
|
|
194
|
+
completeOAuthLogin(): Promise<AuthUser>;
|
|
185
195
|
logout(): Promise<void>;
|
|
186
196
|
refresh(): Promise<void>;
|
|
187
197
|
fetchUser(): Promise<AuthUser | null>;
|
|
@@ -230,10 +240,11 @@ declare class NajmAuthClient {
|
|
|
230
240
|
private handleTabMessage;
|
|
231
241
|
private notify;
|
|
232
242
|
private emit;
|
|
243
|
+
private validateReturnTo;
|
|
233
244
|
}
|
|
234
245
|
/**
|
|
235
246
|
* Factory function to create an auth client.
|
|
236
247
|
*/
|
|
237
248
|
declare function createAuthClient(config: AuthClientConfig): NajmAuthClient;
|
|
238
249
|
|
|
239
|
-
export { AuthError as A, type DecodedToken as D, FetchClient as F, type HydrateSession as H, NajmAuthClient as N, type RetryConfig as R, type SyncPayload as S, type TabSyncMessage as T, type AuthClientConfig as a, type AuthEventMap as b, createAuthClient as c, type AuthState as d, type AuthUser as e, type AuthEvent as f, type AuthEventHandler as g, type ServerResponse as h, type TokenPair as i, type RequestOptions as j };
|
|
250
|
+
export { AuthError as A, type DecodedToken as D, FetchClient as F, type HydrateSession as H, NajmAuthClient as N, type OAuthProvider as O, type RetryConfig as R, type SyncPayload as S, type TabSyncMessage as T, type AuthClientConfig as a, type AuthEventMap as b, createAuthClient as c, type AuthState as d, type AuthUser as e, type AuthEvent as f, type AuthEventHandler as g, type ServerResponse as h, type TokenPair as i, type RequestOptions as j, type OAuthLoginOptions as k };
|
package/dist/client/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { D as DecodedToken, T as TabSyncMessage, S as SyncPayload } from '../NajmAuthClient-
|
|
2
|
-
export { a as AuthClientConfig, A as AuthError, f as AuthEvent, g as AuthEventHandler, b as AuthEventMap, d as AuthState, e as AuthUser, F as FetchClient, H as HydrateSession, N as NajmAuthClient, j as RequestOptions, R as RetryConfig, h as ServerResponse, i as TokenPair, c as createAuthClient } from '../NajmAuthClient-
|
|
1
|
+
import { D as DecodedToken, T as TabSyncMessage, S as SyncPayload } from '../NajmAuthClient-B9dGk9MH.js';
|
|
2
|
+
export { a as AuthClientConfig, A as AuthError, f as AuthEvent, g as AuthEventHandler, b as AuthEventMap, d as AuthState, e as AuthUser, F as FetchClient, H as HydrateSession, N as NajmAuthClient, k as OAuthLoginOptions, O as OAuthProvider, j as RequestOptions, R as RetryConfig, h as ServerResponse, i as TokenPair, c as createAuthClient } from '../NajmAuthClient-B9dGk9MH.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Decode a JWT token payload without verification.
|
package/dist/client/index.js
CHANGED
|
@@ -138,7 +138,7 @@ function decodeToken(token) {
|
|
|
138
138
|
}
|
|
139
139
|
__name(decodeToken, "decodeToken");
|
|
140
140
|
function isTokenExpired(decoded) {
|
|
141
|
-
if (!decoded.exp) return
|
|
141
|
+
if (!decoded.exp) return true;
|
|
142
142
|
return Date.now() / 1e3 >= decoded.exp;
|
|
143
143
|
}
|
|
144
144
|
__name(isTokenExpired, "isTokenExpired");
|
|
@@ -264,6 +264,43 @@ var NajmAuthClient = class _NajmAuthClient {
|
|
|
264
264
|
);
|
|
265
265
|
return res.data;
|
|
266
266
|
}
|
|
267
|
+
getOAuthLoginUrl(provider, options = {}) {
|
|
268
|
+
const baseURL = this.config.baseURL.replace(/\/$/, "");
|
|
269
|
+
const authPrefix = this.prefix.startsWith("/") ? this.prefix : `/${this.prefix}`;
|
|
270
|
+
const raw = `${baseURL}${authPrefix}/oauth/${provider}/start`;
|
|
271
|
+
const absolute = /^[a-z][a-z\d+.-]*:\/\//i.test(raw);
|
|
272
|
+
const url = new URL(raw, absolute ? void 0 : "https://najm.invalid");
|
|
273
|
+
if (options.returnTo) {
|
|
274
|
+
url.searchParams.set("returnTo", this.validateReturnTo(options.returnTo));
|
|
275
|
+
}
|
|
276
|
+
return absolute ? url.toString() : `${url.pathname}${url.search}`;
|
|
277
|
+
}
|
|
278
|
+
loginWithOAuth(provider, options) {
|
|
279
|
+
if (typeof window === "undefined") {
|
|
280
|
+
throw new Error("OAuth login requires a browser environment");
|
|
281
|
+
}
|
|
282
|
+
window.location.assign(this.getOAuthLoginUrl(provider, options));
|
|
283
|
+
}
|
|
284
|
+
loginWithGoogle(options) {
|
|
285
|
+
this.loginWithOAuth("google", options);
|
|
286
|
+
}
|
|
287
|
+
async linkOAuthAccount(provider, options = {}) {
|
|
288
|
+
const query = options.returnTo ? `?${new URLSearchParams({ returnTo: this.validateReturnTo(options.returnTo) })}` : "";
|
|
289
|
+
const res = await this.api.post(
|
|
290
|
+
`${this.prefix}/oauth/${provider}/link${query}`
|
|
291
|
+
);
|
|
292
|
+
if (!res.data?.authorizationUrl) throw new Error("OAuth provider did not return an authorization URL");
|
|
293
|
+
if (typeof window === "undefined") throw new Error("OAuth linking requires a browser environment");
|
|
294
|
+
window.location.assign(res.data.authorizationUrl);
|
|
295
|
+
}
|
|
296
|
+
async completeOAuthLogin() {
|
|
297
|
+
await this.refresh();
|
|
298
|
+
const user = await this.fetchUser();
|
|
299
|
+
if (!user) throw new Error("OAuth session could not be completed");
|
|
300
|
+
this.tabSync?.broadcastSync(this.getSyncPayload());
|
|
301
|
+
this.emit("login", user);
|
|
302
|
+
return user;
|
|
303
|
+
}
|
|
267
304
|
async logout() {
|
|
268
305
|
this.resetState();
|
|
269
306
|
this.tabSync?.broadcastLogout();
|
|
@@ -555,6 +592,18 @@ var NajmAuthClient = class _NajmAuthClient {
|
|
|
555
592
|
this.eventListeners.get("stateChange")?.forEach((h) => h(this.state));
|
|
556
593
|
}
|
|
557
594
|
}
|
|
595
|
+
validateReturnTo(value) {
|
|
596
|
+
const candidate = value.trim();
|
|
597
|
+
if (!candidate.startsWith("/") || candidate.startsWith("//") || candidate.includes("\\")) {
|
|
598
|
+
throw new Error("returnTo must be a same-origin path");
|
|
599
|
+
}
|
|
600
|
+
const base = new URL("https://najm.invalid");
|
|
601
|
+
const parsed = new URL(candidate, base);
|
|
602
|
+
if (parsed.origin !== base.origin || parsed.username || parsed.password) {
|
|
603
|
+
throw new Error("returnTo must be a same-origin path");
|
|
604
|
+
}
|
|
605
|
+
return `${parsed.pathname}${parsed.search}${parsed.hash}`;
|
|
606
|
+
}
|
|
558
607
|
};
|
|
559
608
|
function createAuthClient(config) {
|
|
560
609
|
return new NajmAuthClient(config);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import * as react from 'react';
|
|
3
3
|
import { ReactNode, CSSProperties, ReactElement } from 'react';
|
|
4
|
-
import { N as NajmAuthClient, H as HydrateSession, d as AuthState, e as AuthUser, A as AuthError, f as AuthEvent, b as AuthEventMap } from '../../NajmAuthClient-
|
|
4
|
+
import { N as NajmAuthClient, H as HydrateSession, d as AuthState, e as AuthUser, A as AuthError, k as OAuthLoginOptions, f as AuthEvent, b as AuthEventMap } from '../../NajmAuthClient-B9dGk9MH.js';
|
|
5
5
|
|
|
6
6
|
interface AuthProviderProps {
|
|
7
7
|
client: NajmAuthClient;
|
|
@@ -162,6 +162,27 @@ interface UseChangePasswordReturn {
|
|
|
162
162
|
}
|
|
163
163
|
declare function useChangePassword(opts?: UseChangePasswordOptions): UseChangePasswordReturn;
|
|
164
164
|
|
|
165
|
+
interface UseGoogleLoginOptions {
|
|
166
|
+
onError?: (error: AuthError | Error) => void;
|
|
167
|
+
}
|
|
168
|
+
interface UseGoogleLoginReturn {
|
|
169
|
+
loginWithGoogle: (options?: OAuthLoginOptions) => void;
|
|
170
|
+
linkGoogle: (options?: OAuthLoginOptions) => Promise<void>;
|
|
171
|
+
isRedirecting: boolean;
|
|
172
|
+
error: AuthError | Error | null;
|
|
173
|
+
}
|
|
174
|
+
declare function useGoogleLogin(options?: UseGoogleLoginOptions): UseGoogleLoginReturn;
|
|
175
|
+
|
|
176
|
+
interface UseOAuthCallbackOptions {
|
|
177
|
+
onSuccess?: (user: AuthUser) => void;
|
|
178
|
+
onError?: (error: AuthError | Error) => void;
|
|
179
|
+
}
|
|
180
|
+
declare function useOAuthCallback(options?: UseOAuthCallbackOptions): {
|
|
181
|
+
complete: () => Promise<AuthUser>;
|
|
182
|
+
isLoading: boolean;
|
|
183
|
+
error: AuthError | Error;
|
|
184
|
+
};
|
|
185
|
+
|
|
165
186
|
/**
|
|
166
187
|
* Subscribe to a specific auth event. Cleans up on unmount.
|
|
167
188
|
* Uses a stable ref for the handler to avoid re-subscribing on every render.
|
|
@@ -537,4 +558,22 @@ interface RedirectToLoginProps {
|
|
|
537
558
|
*/
|
|
538
559
|
declare function RedirectToLogin({ to, preserveFrom }: RedirectToLoginProps): any;
|
|
539
560
|
|
|
540
|
-
|
|
561
|
+
interface GoogleLoginButtonProps extends OAuthLoginOptions {
|
|
562
|
+
children: ReactNode;
|
|
563
|
+
onError?: (error: Error) => void;
|
|
564
|
+
}
|
|
565
|
+
declare function GoogleLoginButton({ children, returnTo, onError }: GoogleLoginButtonProps): ReactElement<{
|
|
566
|
+
onClick?: (event: unknown) => void;
|
|
567
|
+
disabled?: boolean;
|
|
568
|
+
}, string | react.JSXElementConstructor<any>>;
|
|
569
|
+
|
|
570
|
+
interface OAuthCallbackProps {
|
|
571
|
+
fallback?: ReactNode;
|
|
572
|
+
errorFallback?: ReactNode | ((props: {
|
|
573
|
+
error: Error;
|
|
574
|
+
}) => ReactNode);
|
|
575
|
+
defaultRedirect?: string;
|
|
576
|
+
}
|
|
577
|
+
declare function OAuthCallback({ fallback, errorFallback, defaultRedirect, }: OAuthCallbackProps): react_jsx_runtime.JSX.Element;
|
|
578
|
+
|
|
579
|
+
export { AuthBoundary, type AuthEventEntry, AuthGate, AuthLoading, AuthProvider, Can, GoogleLoginButton, IfAuth, LoginButton, OAuthCallback, PermissionList, Protected, RedirectToLogin, Role, type SessionStatus, SignOutButton, SignedIn, SignedOut, UserAvatar, UserEmail, UserName, UserRole, useAuth, useAuthClient, useAuthEvent, useAuthEvents, useChangePassword, useForgotPassword, useGoogleLogin, useLogin, useLogout, useOAuthCallback, usePermissions, useRegister, useResetPassword, useSession, useUser };
|
|
@@ -310,11 +310,74 @@ function useChangePassword(opts) {
|
|
|
310
310
|
}
|
|
311
311
|
__name(useChangePassword, "useChangePassword");
|
|
312
312
|
|
|
313
|
+
// src/client/react/useGoogleLogin.ts
|
|
314
|
+
import { useCallback as useCallback7, useState as useState8 } from "react";
|
|
315
|
+
function useGoogleLogin(options) {
|
|
316
|
+
const client = useAuthClient();
|
|
317
|
+
const [isRedirecting, setIsRedirecting] = useState8(false);
|
|
318
|
+
const [error, setError] = useState8(null);
|
|
319
|
+
const fail = useCallback7((value) => {
|
|
320
|
+
const next = value instanceof Error ? value : new Error(String(value));
|
|
321
|
+
setIsRedirecting(false);
|
|
322
|
+
setError(next);
|
|
323
|
+
options?.onError?.(next);
|
|
324
|
+
}, [options?.onError]);
|
|
325
|
+
const loginWithGoogle = useCallback7((loginOptions) => {
|
|
326
|
+
setError(null);
|
|
327
|
+
setIsRedirecting(true);
|
|
328
|
+
try {
|
|
329
|
+
client.loginWithGoogle(loginOptions);
|
|
330
|
+
} catch (value) {
|
|
331
|
+
fail(value);
|
|
332
|
+
}
|
|
333
|
+
}, [client, fail]);
|
|
334
|
+
const linkGoogle = useCallback7(async (loginOptions) => {
|
|
335
|
+
setError(null);
|
|
336
|
+
setIsRedirecting(true);
|
|
337
|
+
try {
|
|
338
|
+
await client.linkOAuthAccount("google", loginOptions);
|
|
339
|
+
} catch (value) {
|
|
340
|
+
fail(value);
|
|
341
|
+
}
|
|
342
|
+
}, [client, fail]);
|
|
343
|
+
return { loginWithGoogle, linkGoogle, isRedirecting, error };
|
|
344
|
+
}
|
|
345
|
+
__name(useGoogleLogin, "useGoogleLogin");
|
|
346
|
+
|
|
347
|
+
// src/client/react/useOAuthCallback.ts
|
|
348
|
+
import { useCallback as useCallback8, useRef as useRef3, useState as useState9 } from "react";
|
|
349
|
+
function useOAuthCallback(options) {
|
|
350
|
+
const client = useAuthClient();
|
|
351
|
+
const promise = useRef3(null);
|
|
352
|
+
const [isLoading, setIsLoading] = useState9(false);
|
|
353
|
+
const [error, setError] = useState9(null);
|
|
354
|
+
const complete = useCallback8(async () => {
|
|
355
|
+
if (promise.current) return promise.current;
|
|
356
|
+
setIsLoading(true);
|
|
357
|
+
setError(null);
|
|
358
|
+
promise.current = client.completeOAuthLogin();
|
|
359
|
+
try {
|
|
360
|
+
const user = await promise.current;
|
|
361
|
+
options?.onSuccess?.(user);
|
|
362
|
+
return user;
|
|
363
|
+
} catch (value) {
|
|
364
|
+
const next = value instanceof Error ? value : new Error(String(value));
|
|
365
|
+
setError(next);
|
|
366
|
+
options?.onError?.(next);
|
|
367
|
+
throw next;
|
|
368
|
+
} finally {
|
|
369
|
+
setIsLoading(false);
|
|
370
|
+
}
|
|
371
|
+
}, [client, options?.onSuccess, options?.onError]);
|
|
372
|
+
return { complete, isLoading, error };
|
|
373
|
+
}
|
|
374
|
+
__name(useOAuthCallback, "useOAuthCallback");
|
|
375
|
+
|
|
313
376
|
// src/client/react/useAuthEvent.ts
|
|
314
|
-
import { useEffect as useEffect3, useRef as
|
|
377
|
+
import { useEffect as useEffect3, useRef as useRef4 } from "react";
|
|
315
378
|
function useAuthEvent(event, handler) {
|
|
316
379
|
const client = useAuthClient();
|
|
317
|
-
const handlerRef =
|
|
380
|
+
const handlerRef = useRef4(handler);
|
|
318
381
|
handlerRef.current = handler;
|
|
319
382
|
useEffect3(() => {
|
|
320
383
|
const listener = /* @__PURE__ */ __name((data) => handlerRef.current(data), "listener");
|
|
@@ -325,7 +388,7 @@ function useAuthEvent(event, handler) {
|
|
|
325
388
|
__name(useAuthEvent, "useAuthEvent");
|
|
326
389
|
|
|
327
390
|
// src/client/react/useAuthEvents.ts
|
|
328
|
-
import { useEffect as useEffect4, useRef as
|
|
391
|
+
import { useEffect as useEffect4, useRef as useRef5, useState as useState10 } from "react";
|
|
329
392
|
var ALL_EVENTS = [
|
|
330
393
|
"login",
|
|
331
394
|
"logout",
|
|
@@ -338,8 +401,8 @@ var ALL_EVENTS = [
|
|
|
338
401
|
function useAuthEvents(opts) {
|
|
339
402
|
const client = useAuthClient();
|
|
340
403
|
const max = opts?.maxEntries ?? 100;
|
|
341
|
-
const [entries, setEntries] =
|
|
342
|
-
const maxRef =
|
|
404
|
+
const [entries, setEntries] = useState10([]);
|
|
405
|
+
const maxRef = useRef5(max);
|
|
343
406
|
maxRef.current = max;
|
|
344
407
|
useEffect4(() => {
|
|
345
408
|
const cleanups = [];
|
|
@@ -619,14 +682,73 @@ function RedirectToLogin({ to = "/login", preserveFrom = true }) {
|
|
|
619
682
|
return null;
|
|
620
683
|
}
|
|
621
684
|
__name(RedirectToLogin, "RedirectToLogin");
|
|
685
|
+
|
|
686
|
+
// src/client/react/GoogleLoginButton.tsx
|
|
687
|
+
import { Children as Children3, cloneElement as cloneElement3, isValidElement as isValidElement3 } from "react";
|
|
688
|
+
function GoogleLoginButton({ children, returnTo, onError }) {
|
|
689
|
+
const { loginWithGoogle, isRedirecting } = useGoogleLogin({ onError });
|
|
690
|
+
const child = Children3.only(children);
|
|
691
|
+
if (!isValidElement3(child)) {
|
|
692
|
+
throw new Error("<GoogleLoginButton> expects a single React element as its child");
|
|
693
|
+
}
|
|
694
|
+
const element = child;
|
|
695
|
+
return cloneElement3(element, {
|
|
696
|
+
onClick: /* @__PURE__ */ __name((event) => {
|
|
697
|
+
element.props.onClick?.(event);
|
|
698
|
+
if (!event?.defaultPrevented) {
|
|
699
|
+
loginWithGoogle({ returnTo });
|
|
700
|
+
}
|
|
701
|
+
}, "onClick"),
|
|
702
|
+
disabled: Boolean(element.props.disabled || isRedirecting)
|
|
703
|
+
});
|
|
704
|
+
}
|
|
705
|
+
__name(GoogleLoginButton, "GoogleLoginButton");
|
|
706
|
+
|
|
707
|
+
// src/client/react/OAuthCallback.tsx
|
|
708
|
+
import { useEffect as useEffect7 } from "react";
|
|
709
|
+
import { Fragment as Fragment14, jsx as jsx16 } from "react/jsx-runtime";
|
|
710
|
+
var safeReturnTo = /* @__PURE__ */ __name((value, fallback) => {
|
|
711
|
+
const safeFallback = fallback.startsWith("/") && !fallback.startsWith("//") && !fallback.includes("\\") ? fallback : "/";
|
|
712
|
+
if (!value || !value.startsWith("/") || value.startsWith("//") || value.includes("\\")) return safeFallback;
|
|
713
|
+
try {
|
|
714
|
+
const base = new URL("https://najm.invalid");
|
|
715
|
+
const parsed = new URL(value, base);
|
|
716
|
+
if (parsed.origin !== base.origin) return safeFallback;
|
|
717
|
+
return `${parsed.pathname}${parsed.search}${parsed.hash}`;
|
|
718
|
+
} catch {
|
|
719
|
+
return safeFallback;
|
|
720
|
+
}
|
|
721
|
+
}, "safeReturnTo");
|
|
722
|
+
function OAuthCallback({
|
|
723
|
+
fallback = null,
|
|
724
|
+
errorFallback = null,
|
|
725
|
+
defaultRedirect = "/"
|
|
726
|
+
}) {
|
|
727
|
+
const { complete, error } = useOAuthCallback();
|
|
728
|
+
useEffect7(() => {
|
|
729
|
+
complete().then(() => {
|
|
730
|
+
if (typeof window === "undefined") return;
|
|
731
|
+
const params = new URLSearchParams(window.location.search);
|
|
732
|
+
window.location.replace(safeReturnTo(params.get("returnTo"), defaultRedirect));
|
|
733
|
+
}).catch(() => {
|
|
734
|
+
});
|
|
735
|
+
}, [complete, defaultRedirect]);
|
|
736
|
+
if (error) {
|
|
737
|
+
return /* @__PURE__ */ jsx16(Fragment14, { children: typeof errorFallback === "function" ? errorFallback({ error }) : errorFallback });
|
|
738
|
+
}
|
|
739
|
+
return /* @__PURE__ */ jsx16(Fragment14, { children: fallback });
|
|
740
|
+
}
|
|
741
|
+
__name(OAuthCallback, "OAuthCallback");
|
|
622
742
|
export {
|
|
623
743
|
AuthBoundary,
|
|
624
744
|
AuthGate,
|
|
625
745
|
AuthLoading,
|
|
626
746
|
AuthProvider,
|
|
627
747
|
Can,
|
|
748
|
+
GoogleLoginButton,
|
|
628
749
|
IfAuth,
|
|
629
750
|
LoginButton,
|
|
751
|
+
OAuthCallback,
|
|
630
752
|
PermissionList,
|
|
631
753
|
Protected,
|
|
632
754
|
RedirectToLogin,
|
|
@@ -644,8 +766,10 @@ export {
|
|
|
644
766
|
useAuthEvents,
|
|
645
767
|
useChangePassword,
|
|
646
768
|
useForgotPassword,
|
|
769
|
+
useGoogleLogin,
|
|
647
770
|
useLogin,
|
|
648
771
|
useLogout,
|
|
772
|
+
useOAuthCallback,
|
|
649
773
|
usePermissions,
|
|
650
774
|
useRegister,
|
|
651
775
|
useResetPassword,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { e as AuthUser, F as FetchClient, R as RetryConfig, N as NajmAuthClient } from '../../NajmAuthClient-
|
|
1
|
+
import { e as AuthUser, F as FetchClient, R as RetryConfig, N as NajmAuthClient } from '../../NajmAuthClient-B9dGk9MH.js';
|
|
2
2
|
export { withAuthMiddleware } from '../edge.js';
|
|
3
3
|
import 'next/server';
|
|
4
4
|
|
|
@@ -588,6 +588,43 @@ var NajmAuthClient = class _NajmAuthClient {
|
|
|
588
588
|
);
|
|
589
589
|
return res.data;
|
|
590
590
|
}
|
|
591
|
+
getOAuthLoginUrl(provider, options = {}) {
|
|
592
|
+
const baseURL = this.config.baseURL.replace(/\/$/, "");
|
|
593
|
+
const authPrefix = this.prefix.startsWith("/") ? this.prefix : `/${this.prefix}`;
|
|
594
|
+
const raw = `${baseURL}${authPrefix}/oauth/${provider}/start`;
|
|
595
|
+
const absolute = /^[a-z][a-z\d+.-]*:\/\//i.test(raw);
|
|
596
|
+
const url = new URL(raw, absolute ? void 0 : "https://najm.invalid");
|
|
597
|
+
if (options.returnTo) {
|
|
598
|
+
url.searchParams.set("returnTo", this.validateReturnTo(options.returnTo));
|
|
599
|
+
}
|
|
600
|
+
return absolute ? url.toString() : `${url.pathname}${url.search}`;
|
|
601
|
+
}
|
|
602
|
+
loginWithOAuth(provider, options) {
|
|
603
|
+
if (typeof window === "undefined") {
|
|
604
|
+
throw new Error("OAuth login requires a browser environment");
|
|
605
|
+
}
|
|
606
|
+
window.location.assign(this.getOAuthLoginUrl(provider, options));
|
|
607
|
+
}
|
|
608
|
+
loginWithGoogle(options) {
|
|
609
|
+
this.loginWithOAuth("google", options);
|
|
610
|
+
}
|
|
611
|
+
async linkOAuthAccount(provider, options = {}) {
|
|
612
|
+
const query = options.returnTo ? `?${new URLSearchParams({ returnTo: this.validateReturnTo(options.returnTo) })}` : "";
|
|
613
|
+
const res = await this.api.post(
|
|
614
|
+
`${this.prefix}/oauth/${provider}/link${query}`
|
|
615
|
+
);
|
|
616
|
+
if (!res.data?.authorizationUrl) throw new Error("OAuth provider did not return an authorization URL");
|
|
617
|
+
if (typeof window === "undefined") throw new Error("OAuth linking requires a browser environment");
|
|
618
|
+
window.location.assign(res.data.authorizationUrl);
|
|
619
|
+
}
|
|
620
|
+
async completeOAuthLogin() {
|
|
621
|
+
await this.refresh();
|
|
622
|
+
const user = await this.fetchUser();
|
|
623
|
+
if (!user) throw new Error("OAuth session could not be completed");
|
|
624
|
+
this.tabSync?.broadcastSync(this.getSyncPayload());
|
|
625
|
+
this.emit("login", user);
|
|
626
|
+
return user;
|
|
627
|
+
}
|
|
591
628
|
async logout() {
|
|
592
629
|
this.resetState();
|
|
593
630
|
this.tabSync?.broadcastLogout();
|
|
@@ -879,6 +916,18 @@ var NajmAuthClient = class _NajmAuthClient {
|
|
|
879
916
|
this.eventListeners.get("stateChange")?.forEach((h) => h(this.state));
|
|
880
917
|
}
|
|
881
918
|
}
|
|
919
|
+
validateReturnTo(value) {
|
|
920
|
+
const candidate = value.trim();
|
|
921
|
+
if (!candidate.startsWith("/") || candidate.startsWith("//") || candidate.includes("\\")) {
|
|
922
|
+
throw new Error("returnTo must be a same-origin path");
|
|
923
|
+
}
|
|
924
|
+
const base = new URL("https://najm.invalid");
|
|
925
|
+
const parsed = new URL(candidate, base);
|
|
926
|
+
if (parsed.origin !== base.origin || parsed.username || parsed.password) {
|
|
927
|
+
throw new Error("returnTo must be a same-origin path");
|
|
928
|
+
}
|
|
929
|
+
return `${parsed.pathname}${parsed.search}${parsed.hash}`;
|
|
930
|
+
}
|
|
882
931
|
};
|
|
883
932
|
function createAuthClient(config) {
|
|
884
933
|
return new NajmAuthClient(config);
|