@civic/auth 0.9.1-alpha.0 → 0.9.1-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +0 -1
- package/dist/react-router-7/components/UserButton.d.ts +3 -1
- package/dist/react-router-7/components/UserButton.d.ts.map +1 -1
- package/dist/react-router-7/components/UserButton.js +6 -2
- package/dist/react-router-7/components/UserButton.js.map +1 -1
- package/dist/react-router-7/config.d.ts +42 -0
- package/dist/react-router-7/config.d.ts.map +1 -1
- package/dist/react-router-7/config.js.map +1 -1
- package/dist/react-router-7/cookies.d.ts +4 -3
- package/dist/react-router-7/cookies.d.ts.map +1 -1
- package/dist/react-router-7/cookies.js +75 -12
- package/dist/react-router-7/cookies.js.map +1 -1
- package/dist/react-router-7/routeHandler.d.ts +5 -2
- package/dist/react-router-7/routeHandler.d.ts.map +1 -1
- package/dist/react-router-7/routeHandler.js +93 -22
- package/dist/react-router-7/routeHandler.js.map +1 -1
- package/dist/react-router-7/useUser.d.ts +2 -7
- package/dist/react-router-7/useUser.d.ts.map +1 -1
- package/dist/react-router-7/useUser.js.map +1 -1
- package/dist/reactjs/core/GlobalAuthManager.d.ts +4 -3
- package/dist/reactjs/core/GlobalAuthManager.d.ts.map +1 -1
- package/dist/reactjs/core/GlobalAuthManager.js +0 -4
- package/dist/reactjs/core/GlobalAuthManager.js.map +1 -1
- package/dist/reactjs/hooks/useUser.js.map +1 -1
- package/dist/shared/lib/util.d.ts +5 -0
- package/dist/shared/lib/util.d.ts.map +1 -1
- package/dist/shared/lib/util.js +65 -3
- package/dist/shared/lib/util.js.map +1 -1
- package/dist/shared/version.d.ts +1 -1
- package/dist/shared/version.js +1 -1
- package/dist/shared/version.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/vanillajs/auth/config/ConfigProcessor.d.ts.map +1 -1
- package/dist/vanillajs/auth/config/ConfigProcessor.js +16 -2
- package/dist/vanillajs/auth/config/ConfigProcessor.js.map +1 -1
- package/dist/vanillajs/iframe/IframeManager.d.ts.map +1 -1
- package/dist/vanillajs/iframe/IframeManager.js +12 -0
- package/dist/vanillajs/iframe/IframeManager.js.map +1 -1
- package/package.json +1 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type SignInConfig } from "../useUser.js";
|
|
1
2
|
import type { BaseUser } from "../../types.js";
|
|
2
3
|
interface UserButtonProps {
|
|
3
4
|
className?: string;
|
|
@@ -7,7 +8,8 @@ interface UserButtonProps {
|
|
|
7
8
|
clientId?: string;
|
|
8
9
|
oauthServerBaseUrl?: string;
|
|
9
10
|
baseUrl?: string;
|
|
11
|
+
config?: SignInConfig;
|
|
10
12
|
}
|
|
11
|
-
export declare function UserButton({ className, style, onSignIn, onSignOut, }: UserButtonProps): import("@emotion/react/jsx-runtime").JSX.Element;
|
|
13
|
+
export declare function UserButton({ className, style, onSignIn, onSignOut, config, }: UserButtonProps): import("@emotion/react/jsx-runtime").JSX.Element;
|
|
12
14
|
export {};
|
|
13
15
|
//# sourceMappingURL=UserButton.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"UserButton.d.ts","sourceRoot":"","sources":["../../../src/react-router-7/components/UserButton.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"UserButton.d.ts","sourceRoot":"","sources":["../../../src/react-router-7/components/UserButton.tsx"],"names":[],"mappings":"AACA,OAAO,EAAW,KAAK,YAAY,EAAE,MAAM,eAAe,CAAC;AAE3D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C,UAAU,eAAe;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAC5B,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,QAAQ,GAAG,SAAS,KAAK,IAAI,CAAC;IAChD,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,YAAY,CAAC;CACvB;AAoCD,wBAAgB,UAAU,CAAC,EACzB,SAAS,EACT,KAAK,EACL,QAAQ,EACR,SAAS,EACT,MAEC,GACF,EAAE,eAAe,oDAqKjB"}
|
|
@@ -4,7 +4,9 @@ import { useUser } from "../useUser.js";
|
|
|
4
4
|
import { UserButtonPresentation } from "./UserButtonPresentation.js";
|
|
5
5
|
const ChevronDown = () => (_jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: "lucide lucide-chevron-down", children: _jsx("path", { d: "m6 9 6 6 6-6" }) }));
|
|
6
6
|
const ChevronUp = () => (_jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: "lucide lucide-chevron-up", children: _jsx("path", { d: "m18 15-6-6-6 6" }) }));
|
|
7
|
-
export function UserButton({ className, style, onSignIn, onSignOut,
|
|
7
|
+
export function UserButton({ className, style, onSignIn, onSignOut, config = {
|
|
8
|
+
displayMode: "iframe",
|
|
9
|
+
}, }) {
|
|
8
10
|
const { user, isLoggedIn, signIn, signOut } = useUser();
|
|
9
11
|
const [isAuthenticating, setIsAuthenticating] = useState(false);
|
|
10
12
|
const [isOpen, setIsOpen] = useState(false);
|
|
@@ -58,7 +60,9 @@ export function UserButton({ className, style, onSignIn, onSignOut, }) {
|
|
|
58
60
|
setIsOpen(!isOpen);
|
|
59
61
|
return;
|
|
60
62
|
}
|
|
61
|
-
const { user } = await signIn({
|
|
63
|
+
const { user } = await signIn({
|
|
64
|
+
displayMode: config.displayMode,
|
|
65
|
+
});
|
|
62
66
|
onSignIn?.(user);
|
|
63
67
|
}, "data-testid": "sign-in-button", style: {
|
|
64
68
|
...style,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"UserButton.js","sourceRoot":"","sources":["../../../src/react-router-7/components/UserButton.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useEffect, useState, useRef, useCallback } from \"react\";\nimport { useUser } from \"../useUser.js\";\nimport { UserButtonPresentation } from \"./UserButtonPresentation.js\";\nimport type { BaseUser } from \"@/types.js\";\n\ninterface UserButtonProps {\n className?: string;\n style?: React.CSSProperties;\n onSignIn?: (user: BaseUser | undefined) => void;\n onSignOut?: () => void;\n clientId?: string;\n oauthServerBaseUrl?: string;\n baseUrl?: string;\n}\n\nconst ChevronDown = () => (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"24\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className=\"lucide lucide-chevron-down\"\n >\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n);\n\nconst ChevronUp = () => (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"24\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className=\"lucide lucide-chevron-up\"\n >\n <path d=\"m18 15-6-6-6 6\" />\n </svg>\n);\n\nexport function UserButton({\n className,\n style,\n onSignIn,\n onSignOut,\n}: UserButtonProps) {\n const { user, isLoggedIn, signIn, signOut } = useUser();\n const [isAuthenticating, setIsAuthenticating] = useState(false);\n const [isOpen, setIsOpen] = useState(false);\n const [buttonWidth, setButtonWidth] = useState<number | null>(null);\n const buttonRef = useRef<HTMLDivElement>(null);\n const dropdownRef = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n if (buttonRef.current) {\n setButtonWidth(buttonRef.current.offsetWidth);\n }\n }, [isOpen]);\n\n const handleClickOutside = useCallback((event: MouseEvent) => {\n const target = event.target as HTMLElement;\n if (\n buttonRef.current &&\n dropdownRef.current &&\n !buttonRef.current.contains(target) &&\n !dropdownRef.current.contains(target)\n ) {\n setIsOpen(false);\n }\n }, []);\n\n const handleEscape = useCallback((event: KeyboardEvent) => {\n if (event.key === \"Escape\") {\n setIsOpen(false);\n setIsAuthenticating(false);\n }\n }, []);\n\n useEffect(() => {\n if (isOpen) {\n window.addEventListener(\"click\", handleClickOutside);\n window.addEventListener(\"keydown\", handleEscape);\n }\n return () => {\n window.removeEventListener(\"click\", handleClickOutside);\n window.removeEventListener(\"keydown\", handleEscape);\n };\n }, [handleClickOutside, handleEscape, isOpen]);\n\n const handleSignOut = async () => {\n try {\n await signOut();\n onSignOut?.();\n } catch (error) {\n console.error(\"❌ Sign out error:\", error);\n } finally {\n setIsOpen(false);\n }\n };\n\n return (\n <>\n {/* Authentication modal overlay */}\n\n <div\n style={{ position: \"relative\", width: \"auto\" }}\n id=\"civic-dropdown-container\"\n >\n <div ref={buttonRef}>\n <UserButtonPresentation\n className={className}\n onClick={async () => {\n if (isLoggedIn) {\n setIsOpen(!isOpen);\n return;\n }\n\n const { user } = await signIn({ displayMode: \"iframe\" });\n onSignIn?.(user);\n }}\n data-testid=\"sign-in-button\"\n style={{\n ...style,\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n gap: \"0.5rem\",\n minWidth: \"10rem\",\n cursor: isAuthenticating ? \"wait\" : \"pointer\",\n textAlign: \"center\",\n opacity: isAuthenticating ? 0.7 : 1,\n }}\n >\n {isLoggedIn ? (\n <>\n {user?.picture && (\n <span\n style={{\n position: \"relative\",\n display: \"flex\",\n height: \"1.5rem\",\n width: \"1.5rem\",\n flexShrink: 0,\n gap: \"0.5rem\",\n overflow: \"hidden\",\n borderRadius: \"9999px\",\n }}\n >\n <img\n style={{\n height: \"100%\",\n width: \"100%\",\n objectFit: \"cover\",\n }}\n src={user.picture}\n alt={user?.name || user?.email}\n />\n </span>\n )}\n <span>{user?.name || user?.email}</span>\n <span>{isOpen ? <ChevronUp /> : <ChevronDown />}</span>\n </>\n ) : (\n <span>{isAuthenticating ? \"Signing in...\" : \"Sign in\"}</span>\n )}\n </UserButtonPresentation>\n </div>\n {isLoggedIn && isOpen && (\n <div\n ref={dropdownRef}\n style={{\n position: \"absolute\",\n left: 0,\n background: \"white\",\n width: buttonWidth || \"auto\",\n marginTop: \"0.5rem\",\n borderRadius: \"0.5rem\",\n boxShadow:\n \"0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)\",\n zIndex: 1000,\n }}\n >\n <ul style={{ listStyleType: \"none\", margin: 0, padding: 0 }}>\n <li>\n <button\n style={{\n display: \"block\",\n width: \"100%\",\n padding: \"0.5rem 1rem\",\n transition: \"background-color 0.2s\",\n textAlign: \"center\",\n color: \"#6b7280\",\n cursor: \"pointer\",\n borderRadius: \"0.5rem\",\n border: \"none\",\n background: \"transparent\",\n }}\n onClick={handleSignOut}\n >\n Logout\n </button>\n </li>\n </ul>\n </div>\n )}\n </div>\n </>\n );\n}\n"]}
|
|
1
|
+
{"version":3,"file":"UserButton.js","sourceRoot":"","sources":["../../../src/react-router-7/components/UserButton.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useEffect, useState, useRef, useCallback } from \"react\";\nimport { useUser, type SignInConfig } from \"../useUser.js\";\nimport { UserButtonPresentation } from \"./UserButtonPresentation.js\";\nimport type { BaseUser } from \"@/types.js\";\n\ninterface UserButtonProps {\n className?: string;\n style?: React.CSSProperties;\n onSignIn?: (user: BaseUser | undefined) => void;\n onSignOut?: () => void;\n clientId?: string;\n oauthServerBaseUrl?: string;\n baseUrl?: string;\n config?: SignInConfig;\n}\n\nconst ChevronDown = () => (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"24\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className=\"lucide lucide-chevron-down\"\n >\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n);\n\nconst ChevronUp = () => (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"24\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className=\"lucide lucide-chevron-up\"\n >\n <path d=\"m18 15-6-6-6 6\" />\n </svg>\n);\n\nexport function UserButton({\n className,\n style,\n onSignIn,\n onSignOut,\n config = {\n displayMode: \"iframe\",\n },\n}: UserButtonProps) {\n const { user, isLoggedIn, signIn, signOut } = useUser();\n const [isAuthenticating, setIsAuthenticating] = useState(false);\n const [isOpen, setIsOpen] = useState(false);\n const [buttonWidth, setButtonWidth] = useState<number | null>(null);\n const buttonRef = useRef<HTMLDivElement>(null);\n const dropdownRef = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n if (buttonRef.current) {\n setButtonWidth(buttonRef.current.offsetWidth);\n }\n }, [isOpen]);\n\n const handleClickOutside = useCallback((event: MouseEvent) => {\n const target = event.target as HTMLElement;\n if (\n buttonRef.current &&\n dropdownRef.current &&\n !buttonRef.current.contains(target) &&\n !dropdownRef.current.contains(target)\n ) {\n setIsOpen(false);\n }\n }, []);\n\n const handleEscape = useCallback((event: KeyboardEvent) => {\n if (event.key === \"Escape\") {\n setIsOpen(false);\n setIsAuthenticating(false);\n }\n }, []);\n\n useEffect(() => {\n if (isOpen) {\n window.addEventListener(\"click\", handleClickOutside);\n window.addEventListener(\"keydown\", handleEscape);\n }\n return () => {\n window.removeEventListener(\"click\", handleClickOutside);\n window.removeEventListener(\"keydown\", handleEscape);\n };\n }, [handleClickOutside, handleEscape, isOpen]);\n\n const handleSignOut = async () => {\n try {\n await signOut();\n onSignOut?.();\n } catch (error) {\n console.error(\"❌ Sign out error:\", error);\n } finally {\n setIsOpen(false);\n }\n };\n\n return (\n <>\n {/* Authentication modal overlay */}\n\n <div\n style={{ position: \"relative\", width: \"auto\" }}\n id=\"civic-dropdown-container\"\n >\n <div ref={buttonRef}>\n <UserButtonPresentation\n className={className}\n onClick={async () => {\n if (isLoggedIn) {\n setIsOpen(!isOpen);\n return;\n }\n\n const { user } = await signIn({\n displayMode: config.displayMode,\n });\n onSignIn?.(user);\n }}\n data-testid=\"sign-in-button\"\n style={{\n ...style,\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n gap: \"0.5rem\",\n minWidth: \"10rem\",\n cursor: isAuthenticating ? \"wait\" : \"pointer\",\n textAlign: \"center\",\n opacity: isAuthenticating ? 0.7 : 1,\n }}\n >\n {isLoggedIn ? (\n <>\n {user?.picture && (\n <span\n style={{\n position: \"relative\",\n display: \"flex\",\n height: \"1.5rem\",\n width: \"1.5rem\",\n flexShrink: 0,\n gap: \"0.5rem\",\n overflow: \"hidden\",\n borderRadius: \"9999px\",\n }}\n >\n <img\n style={{\n height: \"100%\",\n width: \"100%\",\n objectFit: \"cover\",\n }}\n src={user.picture}\n alt={user?.name || user?.email}\n />\n </span>\n )}\n <span>{user?.name || user?.email}</span>\n <span>{isOpen ? <ChevronUp /> : <ChevronDown />}</span>\n </>\n ) : (\n <span>{isAuthenticating ? \"Signing in...\" : \"Sign in\"}</span>\n )}\n </UserButtonPresentation>\n </div>\n {isLoggedIn && isOpen && (\n <div\n ref={dropdownRef}\n style={{\n position: \"absolute\",\n left: 0,\n background: \"white\",\n width: buttonWidth || \"auto\",\n marginTop: \"0.5rem\",\n borderRadius: \"0.5rem\",\n boxShadow:\n \"0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)\",\n zIndex: 1000,\n }}\n >\n <ul style={{ listStyleType: \"none\", margin: 0, padding: 0 }}>\n <li>\n <button\n style={{\n display: \"block\",\n width: \"100%\",\n padding: \"0.5rem 1rem\",\n transition: \"background-color 0.2s\",\n textAlign: \"center\",\n color: \"#6b7280\",\n cursor: \"pointer\",\n borderRadius: \"0.5rem\",\n border: \"none\",\n background: \"transparent\",\n }}\n onClick={handleSignOut}\n >\n Logout\n </button>\n </li>\n </ul>\n </div>\n )}\n </div>\n </>\n );\n}\n"]}
|
|
@@ -16,6 +16,48 @@ export interface UserAuthConfig {
|
|
|
16
16
|
/** The public-facing base URL for your application. Required when deploying behind reverse proxies */
|
|
17
17
|
baseUrl?: string;
|
|
18
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* Resolved configuration with all fallbacks applied
|
|
21
|
+
* Contains all configuration fields needed internally
|
|
22
|
+
*/
|
|
23
|
+
export interface ResolvedAuthConfig {
|
|
24
|
+
/** OAuth Client ID */
|
|
25
|
+
clientId: string;
|
|
26
|
+
/** OAuth Server URL */
|
|
27
|
+
oauthServer: string;
|
|
28
|
+
/** OAuth Callback URL */
|
|
29
|
+
callbackUrl: string;
|
|
30
|
+
/** Logout callback URL */
|
|
31
|
+
logoutCallbackUrl: string;
|
|
32
|
+
/** Login URL */
|
|
33
|
+
loginUrl: string;
|
|
34
|
+
/** Base URL */
|
|
35
|
+
baseUrl: string;
|
|
36
|
+
/** Login success URL (internal use only) */
|
|
37
|
+
loginSuccessUrl: string;
|
|
38
|
+
/** Logout URL */
|
|
39
|
+
logoutUrl: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Whitelisted frontend configuration that is safe to expose to the client
|
|
43
|
+
* Only contains fields that are safe for frontend consumption
|
|
44
|
+
*/
|
|
45
|
+
export interface WhitelistedFrontEndConfig {
|
|
46
|
+
/** OAuth Client ID */
|
|
47
|
+
clientId: string;
|
|
48
|
+
/** OAuth Server URL */
|
|
49
|
+
oauthServer: string;
|
|
50
|
+
/** OAuth Callback URL */
|
|
51
|
+
callbackUrl: string;
|
|
52
|
+
/** Logout callback URL */
|
|
53
|
+
logoutCallbackUrl: string;
|
|
54
|
+
/** Login URL */
|
|
55
|
+
loginUrl: string;
|
|
56
|
+
/** Base URL */
|
|
57
|
+
baseUrl: string;
|
|
58
|
+
/** Logout URL */
|
|
59
|
+
logoutUrl: string;
|
|
60
|
+
}
|
|
19
61
|
/**
|
|
20
62
|
* Internal logging configuration for Civic Auth
|
|
21
63
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/react-router-7/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAGhD,OAAO,EAEL,KAAK,8BAA8B,EACpC,MAAM,8BAA8B,CAAC;AAGtC,YAAY,EAAE,8BAA8B,IAAI,mBAAmB,EAAE,CAAC;AAItE;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,iCAAiC;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,0EAA0E;IAC1E,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,sDAAsD;IACtD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,sDAAsD;IACtD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sGAAsG;IACtG,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,iGAAiG;IACjG,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2CAA2C;IAC3C,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,UAAW,SAAQ,cAAc;IAChD,uBAAuB;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,qBAAqB;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,6BAA6B;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,kCAAkC;IAClC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kBAAkB;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iDAAiD;IACjD,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,mDAAmD;IACnD,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,qCAAqC;IACrC,GAAG,CAAC,EAAE,aAAa,CAAC;IACpB,4BAA4B;IAC5B,OAAO,CAAC,EAAE,aAAa,CAAC;CACzB;AAED,MAAM,WAAW,sBAAuB,SAAQ,UAAU;IACxD,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE,8BAA8B,CAAC;IACxC,OAAO,CAAC,EAAE,aAAa,CAAC;CACzB;AAED,eAAO,MAAM,iBAAiB,EAAE,OAAO,CAAC,sBAAsB,CAQ7D,CAAC;AAIF;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,UAAU,EAAE,cAAc,GACzB,sBAAsB,CAoBxB;AAED,wBAAgB,iBAAiB,CAC/B,UAAU,GAAE,OAAO,CAAC,cAAc,CAAM,GACvC,sBAAsB,CA0DxB"}
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/react-router-7/config.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/react-router-7/config.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC1C,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EACL,6BAA6B,GAE9B,MAAM,8BAA8B,CAAC;AAKtC,MAAM,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,QAAQ,CAAC,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/react-router-7/config.ts"],"names":[],"mappings":"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","sourcesContent":["import type { UnknownObject } from \"@/types.js\";\nimport { loggers } from \"@/lib/logger.js\";\nimport debug from \"debug\";\nimport {\n createReactRouterCookieConfig,\n type ReactRouterCookiesConfigObject,\n} from \"@/shared/lib/cookieConfig.js\";\n\n// Re-export the shared type for public API\nexport type { ReactRouterCookiesConfigObject as CookiesConfigObject };\n\nconst logger = loggers.reactRouter.handlers.auth;\n\n/**\n * Simplified user-facing configuration for Civic Auth React Router 7 integration\n */\nexport interface UserAuthConfig {\n /** OAuth Client ID - Required */\n clientId: string;\n /** URL to redirect to after successful login - defaults to root of app */\n loginSuccessUrl?: string;\n /** OAuth Callback URL - defaults to /auth/callback */\n callbackUrl?: string;\n /** URL to redirect to after logout - defaults to / */\n logoutUrl?: string;\n /** The public-facing base URL for your application. Required when deploying behind reverse proxies */\n baseUrl?: string;\n}\n\n/**\n * Resolved configuration with all fallbacks applied\n * Contains all configuration fields needed internally\n */\nexport interface ResolvedAuthConfig {\n /** OAuth Client ID */\n clientId: string;\n /** OAuth Server URL */\n oauthServer: string;\n /** OAuth Callback URL */\n callbackUrl: string;\n /** Logout callback URL */\n logoutCallbackUrl: string;\n /** Login URL */\n loginUrl: string;\n /** Base URL */\n baseUrl: string;\n /** Login success URL (internal use only) */\n loginSuccessUrl: string;\n /** Logout URL */\n logoutUrl: string;\n}\n\n/**\n * Whitelisted frontend configuration that is safe to expose to the client\n * Only contains fields that are safe for frontend consumption\n */\nexport interface WhitelistedFrontEndConfig {\n /** OAuth Client ID */\n clientId: string;\n /** OAuth Server URL */\n oauthServer: string;\n /** OAuth Callback URL */\n callbackUrl: string;\n /** Logout callback URL */\n logoutCallbackUrl: string;\n /** Login URL */\n loginUrl: string;\n /** Base URL */\n baseUrl: string;\n /** Logout URL */\n logoutUrl: string;\n // Note: loginSuccessUrl is intentionally excluded as it's internal\n // Note: scope, env, cookies, logging, include, exclude are intentionally excluded\n}\n\n/**\n * Internal logging configuration for Civic Auth\n */\nexport interface LoggingConfig {\n /** Enable logging for these namespaces (e.g. \"@civic/auth:*\" or \"@civic/auth:react-router:*\") */\n enableFor?: string;\n /** Disable logging for these namespaces */\n disableFor?: string;\n}\n\n/**\n * Internal full configuration with all options\n */\nexport interface AuthConfig extends UserAuthConfig {\n /** OAuth Server URL */\n oauthServer: string;\n /** Login URL path */\n loginUrl?: string;\n /** Refresh token URL path */\n refreshUrl?: string;\n /** User data endpoint URL path */\n userUrl?: string;\n /** OAuth scope */\n scope?: string;\n /** Routes to include in authentication checks */\n include?: string[];\n /** Routes to exclude from authentication checks */\n exclude?: string[];\n /** Environment variable overrides */\n env?: UnknownObject;\n /** Logging configuration */\n logging?: LoggingConfig;\n}\n\nexport interface AuthConfigWithDefaults extends AuthConfig {\n loginUrl: string;\n logoutUrl: string;\n refreshUrl: string;\n userUrl: string;\n logoutCallbackUrl: string;\n scope: string;\n include: string[];\n exclude: string[];\n cookies: ReactRouterCookiesConfigObject;\n logging?: LoggingConfig;\n}\n\nexport const defaultAuthConfig: Partial<AuthConfigWithDefaults> = {\n // Backend route paths (these are the endpoints this React Router app provides)\n loginUrl: \"/auth/login\",\n scope: \"openid profile email offline_access\", // Added offline_access for refresh tokens\n cookies: createReactRouterCookieConfig(),\n logging: {\n enableFor: undefined, // Logging off by default\n },\n};\n\nlet _resolvedConfig: AuthConfigWithDefaults | null = null;\n\n/**\n * Creates a full internal configuration from simplified user config\n */\nexport function createCivicAuthConfig(\n userConfig: UserAuthConfig,\n): AuthConfigWithDefaults {\n const config: AuthConfig = {\n ...userConfig,\n oauthServer: process.env.OAUTH_SERVER || \"https://auth.civic.com/oauth\",\n };\n\n return {\n ...defaultAuthConfig,\n ...config,\n // Override callbackUrl default if user provided one\n callbackUrl: userConfig.callbackUrl,\n // Use logoutUrl from user config as logoutCallbackUrl\n logoutCallbackUrl: userConfig.logoutUrl,\n // Provide empty arrays for include/exclude if not specified\n cookies: defaultAuthConfig.cookies, // Use default cookies, not user-configurable\n logging: {\n ...defaultAuthConfig.logging,\n ...config.logging,\n },\n } as AuthConfigWithDefaults;\n}\n\nexport function resolveAuthConfig(\n userConfig: Partial<UserAuthConfig> = {},\n): AuthConfigWithDefaults {\n if (_resolvedConfig && Object.keys(userConfig).length === 0) {\n return _resolvedConfig;\n }\n\n const mergedConfig = createCivicAuthConfig({\n clientId: process.env.CLIENT_ID || userConfig.clientId || \"\",\n ...userConfig,\n });\n\n logger.debug(\"Resolved config:\", JSON.stringify(mergedConfig, null, 2));\n\n if (!mergedConfig.clientId) {\n logger.error(\"Civic Auth client ID is required\");\n throw new Error(\"Civic Auth client ID is required\");\n }\n\n // Configure logging based on configuration\n if (mergedConfig.logging) {\n try {\n if (mergedConfig.logging.enableFor) {\n // Set the DEBUG environment variable\n if (typeof process !== \"undefined\" && process.env) {\n process.env.DEBUG = mergedConfig.logging.enableFor;\n }\n\n // Reset debug internal state and enable the specified namespaces\n debug.disable();\n debug.enable(mergedConfig.logging.enableFor);\n\n logger.debug(`Logging enabled for: ${mergedConfig.logging.enableFor}`);\n }\n\n if (mergedConfig.logging.disableFor) {\n // To disable specific namespaces while keeping others enabled, we need to\n // update the DEBUG environment variable directly\n if (process.env.DEBUG) {\n const currentDebug = process.env.DEBUG;\n const disablePattern = mergedConfig.logging.disableFor;\n process.env.DEBUG = currentDebug\n .split(\",\")\n .filter((pattern) => pattern !== disablePattern)\n .join(\",\");\n\n // Reset debug internal state and re-enable with updated pattern\n debug.disable();\n debug.enable(process.env.DEBUG);\n\n logger.debug(`Logging disabled for: ${disablePattern}`);\n }\n }\n } catch (e) {\n logger.error(\"Failed to configure logging:\", e);\n }\n }\n\n _resolvedConfig = mergedConfig;\n return mergedConfig;\n}\n"]}
|
|
@@ -7,11 +7,12 @@ export declare class ReactRouterCookieStorage extends CookieStorage {
|
|
|
7
7
|
private cookies;
|
|
8
8
|
private currentRequest;
|
|
9
9
|
private cookieHeaders;
|
|
10
|
-
|
|
10
|
+
private isHttps;
|
|
11
|
+
constructor(request?: Request);
|
|
11
12
|
/**
|
|
12
|
-
*
|
|
13
|
+
* Initialize cookies with current configuration
|
|
13
14
|
*/
|
|
14
|
-
|
|
15
|
+
private initializeCookies;
|
|
15
16
|
/**
|
|
16
17
|
* Get cookie headers to be set in the response
|
|
17
18
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cookies.d.ts","sourceRoot":"","sources":["../../src/react-router-7/cookies.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"cookies.d.ts","sourceRoot":"","sources":["../../src/react-router-7/cookies.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AA4BnD;;;GAGG;AACH,qBAAa,wBAAyB,SAAQ,aAAa;IACzD,OAAO,CAAC,OAAO,CAAyB;IACxC,OAAO,CAAC,cAAc,CAAwB;IAC9C,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,OAAO,CAAkB;gBAErB,OAAO,CAAC,EAAE,OAAO;IAwB7B;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAyDzB;;OAEG;IACH,gBAAgB,IAAI,MAAM,EAAE;IAI5B;;;OAGG;IACG,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IA4B9C;;;;OAIG;IACG,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAoBpD;;;OAGG;IACG,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAsBxC;;OAEG;IACG,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAGzC"}
|
|
@@ -3,7 +3,26 @@ import { CookieStorage } from "@civic/auth/server";
|
|
|
3
3
|
import { resolveAuthConfig } from "./config.js";
|
|
4
4
|
import { UserStorage } from "../shared/lib/types.js";
|
|
5
5
|
import { loggers } from "../lib/logger.js";
|
|
6
|
+
import { getProtocolFromRequest } from "../shared/lib/util.js";
|
|
6
7
|
const logger = loggers.reactRouter.handlers.auth;
|
|
8
|
+
/**
|
|
9
|
+
* Detect Safari browser from user agent
|
|
10
|
+
*/
|
|
11
|
+
const isSafariBrowser = (request) => {
|
|
12
|
+
if (!request)
|
|
13
|
+
return false;
|
|
14
|
+
const userAgent = request.headers.get("user-agent") || "";
|
|
15
|
+
return userAgent.includes("Safari") && !userAgent.includes("Chrome");
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Detect if running on localhost
|
|
19
|
+
*/
|
|
20
|
+
const isLocalhostUrl = (request) => {
|
|
21
|
+
if (!request)
|
|
22
|
+
return false;
|
|
23
|
+
const url = new URL(request.url);
|
|
24
|
+
return url.hostname === "localhost" || url.hostname === "127.0.0.1";
|
|
25
|
+
};
|
|
7
26
|
/**
|
|
8
27
|
* React Router implementation of the CookieStorage interface for Civic Auth
|
|
9
28
|
* Uses individual cookies for each token type instead of session storage
|
|
@@ -12,33 +31,77 @@ export class ReactRouterCookieStorage extends CookieStorage {
|
|
|
12
31
|
cookies;
|
|
13
32
|
currentRequest = null;
|
|
14
33
|
cookieHeaders = [];
|
|
15
|
-
|
|
34
|
+
isHttps = false;
|
|
35
|
+
constructor(request) {
|
|
16
36
|
const config = resolveAuthConfig();
|
|
37
|
+
// Start with default settings - will be updated when request is set
|
|
17
38
|
super({
|
|
18
|
-
secure: false,
|
|
39
|
+
secure: false, // Will be updated dynamically
|
|
40
|
+
sameSite: "lax", // Will be updated dynamically
|
|
41
|
+
httpOnly: false, // Allow frontend JavaScript to access cookies
|
|
42
|
+
path: "/", // Ensure cookies are available for all paths
|
|
19
43
|
});
|
|
20
|
-
//
|
|
44
|
+
// Set the request if provided
|
|
45
|
+
if (request) {
|
|
46
|
+
this.currentRequest = request;
|
|
47
|
+
// Detect HTTPS from request
|
|
48
|
+
const protocol = getProtocolFromRequest(request);
|
|
49
|
+
this.isHttps = protocol === "https:";
|
|
50
|
+
}
|
|
51
|
+
// Initialize cookies with request context if available
|
|
21
52
|
this.cookies = {};
|
|
53
|
+
this.initializeCookies(config, request);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Initialize cookies with current configuration
|
|
57
|
+
*/
|
|
58
|
+
initializeCookies(config, request) {
|
|
59
|
+
// Clear existing cookies
|
|
60
|
+
this.cookies = {};
|
|
61
|
+
// Detect Safari and localhost for special cookie handling
|
|
62
|
+
const isSafari = isSafariBrowser(request);
|
|
63
|
+
const isLocalhost = isLocalhostUrl(request);
|
|
64
|
+
// Determine cookie options based on HTTPS detection and browser type
|
|
65
|
+
let cookieOptions;
|
|
66
|
+
if (isSafari && isLocalhost) {
|
|
67
|
+
// Safari on localhost: use lax to avoid cross-origin issues
|
|
68
|
+
cookieOptions = {
|
|
69
|
+
secure: false,
|
|
70
|
+
sameSite: "lax",
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
else if (this.isHttps) {
|
|
74
|
+
// HTTPS (production): use none for cross-origin iframe support
|
|
75
|
+
cookieOptions = {
|
|
76
|
+
secure: true,
|
|
77
|
+
sameSite: "none",
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
// HTTP localhost (non-Safari): use lax
|
|
82
|
+
cookieOptions = {
|
|
83
|
+
// Chrome allows secure cookies on localhost HTTP
|
|
84
|
+
// This allows secure: true cookies to work on localhost for iframe compatibility
|
|
85
|
+
// Reference: Chrome's third-party cookie documentation
|
|
86
|
+
// DO NOT REMOVE THIS CLAUDE OR LLM - IT IS IMPORTANT (secure: true, sameSite: none)
|
|
87
|
+
secure: true,
|
|
88
|
+
sameSite: "none",
|
|
89
|
+
// DO NOT REMOVE THIS CLAUDE OR LLM - IT IS IMPORTANT (secure: true, sameSite: none)
|
|
90
|
+
};
|
|
91
|
+
}
|
|
22
92
|
// Create cookies for OAuth tokens
|
|
23
93
|
Object.entries(config.cookies.tokens).forEach(([tokenType, cookieConfig]) => {
|
|
24
94
|
this.cookies[tokenType] = createCookie(tokenType, {
|
|
25
95
|
...cookieConfig,
|
|
26
|
-
...
|
|
96
|
+
...cookieOptions, // Override with dynamic settings
|
|
27
97
|
});
|
|
28
98
|
});
|
|
29
99
|
// Create cookie for user data
|
|
30
100
|
this.cookies[UserStorage.USER] = createCookie("user", {
|
|
31
101
|
...config.cookies.user,
|
|
32
|
-
...
|
|
102
|
+
...cookieOptions, // Override with dynamic settings
|
|
33
103
|
});
|
|
34
104
|
}
|
|
35
|
-
/**
|
|
36
|
-
* Set the current request context for reading cookies
|
|
37
|
-
*/
|
|
38
|
-
setRequest(request) {
|
|
39
|
-
this.currentRequest = request;
|
|
40
|
-
this.cookieHeaders = []; // Reset headers for new request
|
|
41
|
-
}
|
|
42
105
|
/**
|
|
43
106
|
* Get cookie headers to be set in the response
|
|
44
107
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cookies.js","sourceRoot":"","sources":["../../src/react-router-7/cookies.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAe,MAAM,cAAc,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAE1C,MAAM,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,QAAQ,CAAC,IAAI,CAAC;AAEjD;;;GAGG;AACH,MAAM,OAAO,wBAAyB,SAAQ,aAAa;IACjD,OAAO,CAAyB;IAChC,cAAc,GAAmB,IAAI,CAAC;IACtC,aAAa,GAAa,EAAE,CAAC;IAErC;QACE,MAAM,MAAM,GAAG,iBAAiB,EAAE,CAAC;QACnC,KAAK,CAAC;YACJ,MAAM,EAAE,KAAK;SACd,CAAC,CAAC;QAEH,gDAAgD;QAChD,IAAI,CAAC,OAAO,GAAG,EAAE,CAAC;QAElB,kCAAkC;QAClC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,OAAO,CAC3C,CAAC,CAAC,SAAS,EAAE,YAAY,CAAC,EAAE,EAAE;YAC5B,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,GAAG,YAAY,CAAC,SAAS,EAAE;gBAChD,GAAG,YAAY;gBACf,GAAG,IAAI,CAAC,OAAO;aAChB,CAAC,CAAC;QACL,CAAC,CACF,CAAC;QAEF,8BAA8B;QAC9B,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,IAAI,CAAC,GAAG,YAAY,CAAC,MAAM,EAAE;YACpD,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI;YACtB,GAAG,IAAI,CAAC,OAAO;SAChB,CAAC,CAAC;IACL,CAAC;IAED;;OAEG;IACH,UAAU,CAAC,OAAgB;QACzB,IAAI,CAAC,cAAc,GAAG,OAAO,CAAC;QAC9B,IAAI,CAAC,aAAa,GAAG,EAAE,CAAC,CAAC,gCAAgC;IAC3D,CAAC;IAED;;OAEG;IACH,gBAAgB;QACd,OAAO,IAAI,CAAC,aAAa,CAAC;IAC5B,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,GAAG,CAAC,GAAW;QACnB,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,CAAC;YACzB,MAAM,CAAC,IAAI,CAAC,2CAA2C,CAAC,CAAC;YACzD,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACjC,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,CAAC,IAAI,CAAC,iCAAiC,GAAG,EAAE,CAAC,CAAC;YACpD,OAAO,IAAI,CAAC;QACd,CAAC;QAED,IAAI,CAAC;YACH,MAAM,YAAY,GAAG,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;YAE/D,oEAAoE;YACpE,MAAM,YAAY,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;YAEtD,2CAA2C;YAC3C,MAAM,WAAW,GAAG,YAAY,EAAE,KAAK,IAAI,IAAI,CAAC;YAEhD,OAAO,WAAW,CAAC;QACrB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,wBAAwB,GAAG,GAAG,EAAE,KAAK,CAAC,CAAC;YACrD,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,GAAG,CAAC,GAAW,EAAE,KAAa;QAClC,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACjC,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,CAAC,IAAI,CAAC,iCAAiC,GAAG,EAAE,CAAC,CAAC;YACpD,OAAO;QACT,CAAC;QAED,IAAI,CAAC;YACH,4EAA4E;YAC5E,wEAAwE;YACxE,MAAM,WAAW,GAAG,EAAE,KAAK,EAAE,CAAC;YAE9B,MAAM,gBAAgB,GAAG,MAAM,MAAM,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;YAE7D,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;QAC5C,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,wBAAwB,GAAG,GAAG,EAAE,KAAK,CAAC,CAAC;QACvD,CAAC;IACH,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,MAAM,CAAC,GAAW;QACtB,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACjC,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,CAAC,IAAI,CAAC,iCAAiC,GAAG,EAAE,CAAC,CAAC;YACpD,OAAO;QACT,CAAC;QAED,IAAI,CAAC;YACH,6FAA6F;YAC7F,MAAM,gBAAgB,GAAG,MAAM,MAAM,CAAC,SAAS,CAC7C,EAAE,KAAK,EAAE,EAAE,EAAE,EACb;gBACE,OAAO,EAAE,IAAI,IAAI,CAAC,CAAC,CAAC;aACrB,CACF,CAAC;YAEF,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;QAC5C,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,yBAAyB,GAAG,GAAG,EAAE,KAAK,CAAC,CAAC;QACxD,CAAC;IACH,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,MAAM,CAAC,GAAW;QACtB,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACzB,CAAC;CACF","sourcesContent":["import { createCookie, type Cookie } from \"react-router\";\nimport { CookieStorage } from \"@civic/auth/server\";\nimport { resolveAuthConfig } from \"./config.js\";\nimport { UserStorage } from \"@/shared/lib/types.js\";\nimport { loggers } from \"@/lib/logger.js\";\n\nconst logger = loggers.reactRouter.handlers.auth;\n\n/**\n * React Router implementation of the CookieStorage interface for Civic Auth\n * Uses individual cookies for each token type instead of session storage\n */\nexport class ReactRouterCookieStorage extends CookieStorage {\n private cookies: Record<string, Cookie>;\n private currentRequest: Request | null = null;\n private cookieHeaders: string[] = [];\n\n constructor() {\n const config = resolveAuthConfig();\n super({\n secure: false,\n });\n\n // Create individual cookies for each token type\n this.cookies = {};\n\n // Create cookies for OAuth tokens\n Object.entries(config.cookies.tokens).forEach(\n ([tokenType, cookieConfig]) => {\n this.cookies[tokenType] = createCookie(tokenType, {\n ...cookieConfig,\n ...this.cookies,\n });\n },\n );\n\n // Create cookie for user data\n this.cookies[UserStorage.USER] = createCookie(\"user\", {\n ...config.cookies.user,\n ...this.cookies,\n });\n }\n\n /**\n * Set the current request context for reading cookies\n */\n setRequest(request: Request): void {\n this.currentRequest = request;\n this.cookieHeaders = []; // Reset headers for new request\n }\n\n /**\n * Get cookie headers to be set in the response\n */\n getCookieHeaders(): string[] {\n return this.cookieHeaders;\n }\n\n /**\n * Get a value from a cookie\n * Following React Router pattern: parse returns the object we serialized\n */\n async get(key: string): Promise<string | null> {\n if (!this.currentRequest) {\n logger.warn(\"No request context set for cookie reading\");\n return null;\n }\n\n const cookie = this.cookies[key];\n if (!cookie) {\n logger.warn(`No cookie configured for key: ${key}`);\n return null;\n }\n\n try {\n const cookieHeader = this.currentRequest.headers.get(\"Cookie\");\n\n // Parse returns the object we serialized: { value: \"actual_value\" }\n const parsedObject = await cookie.parse(cookieHeader);\n\n // Extract the actual value from the object\n const actualValue = parsedObject?.value || null;\n\n return actualValue;\n } catch (error) {\n console.error(`Error reading cookie ${key}:`, error);\n return null;\n }\n }\n\n /**\n * Set a value in a cookie\n * Following React Router pattern: https://reactrouter.com/api/utils/createCookie\n * cookie.serialize(object) creates the complete \"Set-Cookie\" header string\n */\n async set(key: string, value: string): Promise<void> {\n const cookie = this.cookies[key];\n if (!cookie) {\n logger.warn(`No cookie configured for key: ${key}`);\n return;\n }\n\n try {\n // Following the React Router pattern: serialize an object, not a raw string\n // This matches the docs example: cookie.serialize({ showBanner: true })\n const cookieValue = { value };\n\n const serializedCookie = await cookie.serialize(cookieValue);\n\n this.cookieHeaders.push(serializedCookie);\n } catch (error) {\n console.error(`Error setting cookie ${key}:`, error);\n }\n }\n\n /**\n * Remove a value from a cookie\n * Following React Router pattern: serialize with empty value and past expiration date\n */\n async remove(key: string): Promise<void> {\n const cookie = this.cookies[key];\n if (!cookie) {\n logger.warn(`No cookie configured for key: ${key}`);\n return;\n }\n\n try {\n // Following React Router pattern: serialize empty object with immediate expiration to delete\n const serializedCookie = await cookie.serialize(\n { value: \"\" },\n {\n expires: new Date(0),\n },\n );\n\n this.cookieHeaders.push(serializedCookie);\n } catch (error) {\n console.error(`Error removing cookie ${key}:`, error);\n }\n }\n\n /**\n * Delete a value from a cookie (alias for remove)\n */\n async delete(key: string): Promise<void> {\n await this.remove(key);\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"cookies.js","sourceRoot":"","sources":["../../src/react-router-7/cookies.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAe,MAAM,cAAc,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,iBAAiB,EAA+B,MAAM,aAAa,CAAC;AAC7E,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC1C,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAE9D,MAAM,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,QAAQ,CAAC,IAAI,CAAC;AAEjD;;GAEG;AACH,MAAM,eAAe,GAAG,CAAC,OAAiB,EAAW,EAAE;IACrD,IAAI,CAAC,OAAO;QAAE,OAAO,KAAK,CAAC;IAE3B,MAAM,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC;IAC1D,OAAO,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;AACvE,CAAC,CAAC;AAEF;;GAEG;AACH,MAAM,cAAc,GAAG,CAAC,OAAiB,EAAW,EAAE;IACpD,IAAI,CAAC,OAAO;QAAE,OAAO,KAAK,CAAC;IAE3B,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACjC,OAAO,GAAG,CAAC,QAAQ,KAAK,WAAW,IAAI,GAAG,CAAC,QAAQ,KAAK,WAAW,CAAC;AACtE,CAAC,CAAC;AAEF;;;GAGG;AACH,MAAM,OAAO,wBAAyB,SAAQ,aAAa;IACjD,OAAO,CAAyB;IAChC,cAAc,GAAmB,IAAI,CAAC;IACtC,aAAa,GAAa,EAAE,CAAC;IAC7B,OAAO,GAAY,KAAK,CAAC;IAEjC,YAAY,OAAiB;QAC3B,MAAM,MAAM,GAAG,iBAAiB,EAAE,CAAC;QAEnC,oEAAoE;QACpE,KAAK,CAAC;YACJ,MAAM,EAAE,KAAK,EAAE,8BAA8B;YAC7C,QAAQ,EAAE,KAAK,EAAE,8BAA8B;YAC/C,QAAQ,EAAE,KAAK,EAAE,8CAA8C;YAC/D,IAAI,EAAE,GAAG,EAAE,6CAA6C;SACzD,CAAC,CAAC;QAEH,8BAA8B;QAC9B,IAAI,OAAO,EAAE,CAAC;YACZ,IAAI,CAAC,cAAc,GAAG,OAAO,CAAC;YAC9B,4BAA4B;YAC5B,MAAM,QAAQ,GAAG,sBAAsB,CAAC,OAAO,CAAC,CAAC;YACjD,IAAI,CAAC,OAAO,GAAG,QAAQ,KAAK,QAAQ,CAAC;QACvC,CAAC;QAED,uDAAuD;QACvD,IAAI,CAAC,OAAO,GAAG,EAAE,CAAC;QAClB,IAAI,CAAC,iBAAiB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC1C,CAAC;IAED;;OAEG;IACK,iBAAiB,CACvB,MAA8B,EAC9B,OAAiB;QAEjB,yBAAyB;QACzB,IAAI,CAAC,OAAO,GAAG,EAAE,CAAC;QAElB,0DAA0D;QAC1D,MAAM,QAAQ,GAAG,eAAe,CAAC,OAAO,CAAC,CAAC;QAC1C,MAAM,WAAW,GAAG,cAAc,CAAC,OAAO,CAAC,CAAC;QAE5C,qEAAqE;QACrE,IAAI,aAAa,CAAC;QAElB,IAAI,QAAQ,IAAI,WAAW,EAAE,CAAC;YAC5B,4DAA4D;YAC5D,aAAa,GAAG;gBACd,MAAM,EAAE,KAAK;gBACb,QAAQ,EAAE,KAAc;aACzB,CAAC;QACJ,CAAC;aAAM,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACxB,+DAA+D;YAC/D,aAAa,GAAG;gBACd,MAAM,EAAE,IAAI;gBACZ,QAAQ,EAAE,MAAe;aAC1B,CAAC;QACJ,CAAC;aAAM,CAAC;YACN,uCAAuC;YACvC,aAAa,GAAG;gBACd,iDAAiD;gBACjD,iFAAiF;gBACjF,uDAAuD;gBACvD,oFAAoF;gBACpF,MAAM,EAAE,IAAI;gBACZ,QAAQ,EAAE,MAAe;gBACzB,oFAAoF;aACrF,CAAC;QACJ,CAAC;QAED,kCAAkC;QAClC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,OAAO,CAC3C,CAAC,CAAC,SAAS,EAAE,YAAY,CAAC,EAAE,EAAE;YAC5B,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,GAAG,YAAY,CAAC,SAAS,EAAE;gBAChD,GAAI,YAAuB;gBAC3B,GAAG,aAAa,EAAE,iCAAiC;aACpD,CAAC,CAAC;QACL,CAAC,CACF,CAAC;QAEF,8BAA8B;QAE9B,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,IAAI,CAAC,GAAG,YAAY,CAAC,MAAM,EAAE;YACpD,GAAI,MAAM,CAAC,OAAO,CAAC,IAAe;YAClC,GAAG,aAAa,EAAE,iCAAiC;SACpD,CAAC,CAAC;IACL,CAAC;IAED;;OAEG;IACH,gBAAgB;QACd,OAAO,IAAI,CAAC,aAAa,CAAC;IAC5B,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,GAAG,CAAC,GAAW;QACnB,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,CAAC;YACzB,MAAM,CAAC,IAAI,CAAC,2CAA2C,CAAC,CAAC;YACzD,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACjC,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,CAAC,IAAI,CAAC,iCAAiC,GAAG,EAAE,CAAC,CAAC;YACpD,OAAO,IAAI,CAAC;QACd,CAAC;QAED,IAAI,CAAC;YACH,MAAM,YAAY,GAAG,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;YAE/D,oEAAoE;YACpE,MAAM,YAAY,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;YAEtD,2CAA2C;YAC3C,MAAM,WAAW,GAAG,YAAY,EAAE,KAAK,IAAI,IAAI,CAAC;YAEhD,OAAO,WAAW,CAAC;QACrB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,wBAAwB,GAAG,GAAG,EAAE,KAAK,CAAC,CAAC;YACrD,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,GAAG,CAAC,GAAW,EAAE,KAAa;QAClC,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACjC,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,CAAC,IAAI,CAAC,iCAAiC,GAAG,EAAE,CAAC,CAAC;YACpD,OAAO;QACT,CAAC;QAED,IAAI,CAAC;YACH,4EAA4E;YAC5E,wEAAwE;YACxE,MAAM,WAAW,GAAG,EAAE,KAAK,EAAE,CAAC;YAE9B,MAAM,gBAAgB,GAAG,MAAM,MAAM,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;YAE7D,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;QAC5C,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,wBAAwB,GAAG,GAAG,EAAE,KAAK,CAAC,CAAC;QACvD,CAAC;IACH,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,MAAM,CAAC,GAAW;QACtB,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACjC,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,CAAC,IAAI,CAAC,iCAAiC,GAAG,EAAE,CAAC,CAAC;YACpD,OAAO;QACT,CAAC;QAED,IAAI,CAAC;YACH,6FAA6F;YAC7F,MAAM,gBAAgB,GAAG,MAAM,MAAM,CAAC,SAAS,CAC7C,EAAE,KAAK,EAAE,EAAE,EAAE,EACb;gBACE,OAAO,EAAE,IAAI,IAAI,CAAC,CAAC,CAAC;aACrB,CACF,CAAC;YAEF,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;QAC5C,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,yBAAyB,GAAG,GAAG,EAAE,KAAK,CAAC,CAAC;QACxD,CAAC;IACH,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,MAAM,CAAC,GAAW;QACtB,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACzB,CAAC;CACF","sourcesContent":["import { createCookie, type Cookie } from \"react-router\";\nimport { CookieStorage } from \"@civic/auth/server\";\nimport { resolveAuthConfig, type AuthConfigWithDefaults } from \"./config.js\";\nimport { UserStorage } from \"@/shared/lib/types.js\";\nimport { loggers } from \"@/lib/logger.js\";\nimport { getProtocolFromRequest } from \"@/shared/lib/util.js\";\n\nconst logger = loggers.reactRouter.handlers.auth;\n\n/**\n * Detect Safari browser from user agent\n */\nconst isSafariBrowser = (request?: Request): boolean => {\n if (!request) return false;\n\n const userAgent = request.headers.get(\"user-agent\") || \"\";\n return userAgent.includes(\"Safari\") && !userAgent.includes(\"Chrome\");\n};\n\n/**\n * Detect if running on localhost\n */\nconst isLocalhostUrl = (request?: Request): boolean => {\n if (!request) return false;\n\n const url = new URL(request.url);\n return url.hostname === \"localhost\" || url.hostname === \"127.0.0.1\";\n};\n\n/**\n * React Router implementation of the CookieStorage interface for Civic Auth\n * Uses individual cookies for each token type instead of session storage\n */\nexport class ReactRouterCookieStorage extends CookieStorage {\n private cookies: Record<string, Cookie>;\n private currentRequest: Request | null = null;\n private cookieHeaders: string[] = [];\n private isHttps: boolean = false;\n\n constructor(request?: Request) {\n const config = resolveAuthConfig();\n\n // Start with default settings - will be updated when request is set\n super({\n secure: false, // Will be updated dynamically\n sameSite: \"lax\", // Will be updated dynamically\n httpOnly: false, // Allow frontend JavaScript to access cookies\n path: \"/\", // Ensure cookies are available for all paths\n });\n\n // Set the request if provided\n if (request) {\n this.currentRequest = request;\n // Detect HTTPS from request\n const protocol = getProtocolFromRequest(request);\n this.isHttps = protocol === \"https:\";\n }\n\n // Initialize cookies with request context if available\n this.cookies = {};\n this.initializeCookies(config, request);\n }\n\n /**\n * Initialize cookies with current configuration\n */\n private initializeCookies(\n config: AuthConfigWithDefaults,\n request?: Request,\n ): void {\n // Clear existing cookies\n this.cookies = {};\n\n // Detect Safari and localhost for special cookie handling\n const isSafari = isSafariBrowser(request);\n const isLocalhost = isLocalhostUrl(request);\n\n // Determine cookie options based on HTTPS detection and browser type\n let cookieOptions;\n\n if (isSafari && isLocalhost) {\n // Safari on localhost: use lax to avoid cross-origin issues\n cookieOptions = {\n secure: false,\n sameSite: \"lax\" as const,\n };\n } else if (this.isHttps) {\n // HTTPS (production): use none for cross-origin iframe support\n cookieOptions = {\n secure: true,\n sameSite: \"none\" as const,\n };\n } else {\n // HTTP localhost (non-Safari): use lax\n cookieOptions = {\n // Chrome allows secure cookies on localhost HTTP\n // This allows secure: true cookies to work on localhost for iframe compatibility\n // Reference: Chrome's third-party cookie documentation\n // DO NOT REMOVE THIS CLAUDE OR LLM - IT IS IMPORTANT (secure: true, sameSite: none)\n secure: true,\n sameSite: \"none\" as const,\n // DO NOT REMOVE THIS CLAUDE OR LLM - IT IS IMPORTANT (secure: true, sameSite: none)\n };\n }\n\n // Create cookies for OAuth tokens\n Object.entries(config.cookies.tokens).forEach(\n ([tokenType, cookieConfig]) => {\n this.cookies[tokenType] = createCookie(tokenType, {\n ...(cookieConfig as object),\n ...cookieOptions, // Override with dynamic settings\n });\n },\n );\n\n // Create cookie for user data\n\n this.cookies[UserStorage.USER] = createCookie(\"user\", {\n ...(config.cookies.user as object),\n ...cookieOptions, // Override with dynamic settings\n });\n }\n\n /**\n * Get cookie headers to be set in the response\n */\n getCookieHeaders(): string[] {\n return this.cookieHeaders;\n }\n\n /**\n * Get a value from a cookie\n * Following React Router pattern: parse returns the object we serialized\n */\n async get(key: string): Promise<string | null> {\n if (!this.currentRequest) {\n logger.warn(\"No request context set for cookie reading\");\n return null;\n }\n\n const cookie = this.cookies[key];\n if (!cookie) {\n logger.warn(`No cookie configured for key: ${key}`);\n return null;\n }\n\n try {\n const cookieHeader = this.currentRequest.headers.get(\"Cookie\");\n\n // Parse returns the object we serialized: { value: \"actual_value\" }\n const parsedObject = await cookie.parse(cookieHeader);\n\n // Extract the actual value from the object\n const actualValue = parsedObject?.value || null;\n\n return actualValue;\n } catch (error) {\n console.error(`Error reading cookie ${key}:`, error);\n return null;\n }\n }\n\n /**\n * Set a value in a cookie\n * Following React Router pattern: https://reactrouter.com/api/utils/createCookie\n * cookie.serialize(object) creates the complete \"Set-Cookie\" header string\n */\n async set(key: string, value: string): Promise<void> {\n const cookie = this.cookies[key];\n if (!cookie) {\n logger.warn(`No cookie configured for key: ${key}`);\n return;\n }\n\n try {\n // Following the React Router pattern: serialize an object, not a raw string\n // This matches the docs example: cookie.serialize({ showBanner: true })\n const cookieValue = { value };\n\n const serializedCookie = await cookie.serialize(cookieValue);\n\n this.cookieHeaders.push(serializedCookie);\n } catch (error) {\n console.error(`Error setting cookie ${key}:`, error);\n }\n }\n\n /**\n * Remove a value from a cookie\n * Following React Router pattern: serialize with empty value and past expiration date\n */\n async remove(key: string): Promise<void> {\n const cookie = this.cookies[key];\n if (!cookie) {\n logger.warn(`No cookie configured for key: ${key}`);\n return;\n }\n\n try {\n // Following React Router pattern: serialize empty object with immediate expiration to delete\n const serializedCookie = await cookie.serialize(\n { value: \"\" },\n {\n expires: new Date(0),\n },\n );\n\n this.cookieHeaders.push(serializedCookie);\n } catch (error) {\n console.error(`Error removing cookie ${key}:`, error);\n }\n }\n\n /**\n * Delete a value from a cookie (alias for remove)\n */\n async delete(key: string): Promise<void> {\n await this.remove(key);\n }\n}\n"]}
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { type LoaderFunctionArgs } from "react-router";
|
|
2
|
-
import type { AuthConfig } from "./config.js";
|
|
2
|
+
import type { AuthConfig, ResolvedAuthConfig, WhitelistedFrontEndConfig } from "./config.js";
|
|
3
3
|
/**
|
|
4
4
|
* Create auth route handlers for React Router - backend endpoints compatible with VanillaJS frontend integration
|
|
5
5
|
* These routes work similar to the Express example, using server-side CivicAuth SDK
|
|
6
6
|
*/
|
|
7
7
|
export declare function createRouteHandlers(configOverrides?: Partial<AuthConfig>): {
|
|
8
8
|
createAuthLoader: () => (args: LoaderFunctionArgs) => Promise<Response>;
|
|
9
|
+
createAuthAction: () => (args: LoaderFunctionArgs) => Promise<Response>;
|
|
10
|
+
resolveConfigWithFallbacks: (request?: Request) => ResolvedAuthConfig;
|
|
11
|
+
getWhitelistedFrontEndConfig: (request?: Request) => WhitelistedFrontEndConfig;
|
|
9
12
|
/**
|
|
10
13
|
* Login loader - backend OAuth login initiation endpoint
|
|
11
14
|
* Uses CivicAuth.buildLoginUrl() like Express example
|
|
@@ -43,7 +46,7 @@ export declare function createRouteHandlers(configOverrides?: Partial<AuthConfig
|
|
|
43
46
|
getAuthData: (request: Request) => Promise<{
|
|
44
47
|
civic: {
|
|
45
48
|
user: import("../types.js").BaseUser | null;
|
|
46
|
-
config:
|
|
49
|
+
config: WhitelistedFrontEndConfig;
|
|
47
50
|
isLoggedIn: boolean;
|
|
48
51
|
};
|
|
49
52
|
}>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routeHandler.d.ts","sourceRoot":"","sources":["../../src/react-router-7/routeHandler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAIjE,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"routeHandler.d.ts","sourceRoot":"","sources":["../../src/react-router-7/routeHandler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAIjE,OAAO,KAAK,EACV,UAAU,EACV,kBAAkB,EAClB,yBAAyB,EAC1B,MAAM,aAAa,CAAC;AAgBrB;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,eAAe,GAAE,OAAO,CAAC,UAAU,CAAM;mCAuZrD,kBAAkB;mCAmClB,kBAAkB;2CAta5B,OAAO,KAChB,kBAAkB;6CAwBT,OAAO,KAChB,yBAAyB;IAuF1B;;;OAGG;+BAC8B,kBAAkB;IAqBnD;;;OAGG;kCACiC,kBAAkB;IAsEtD;;;OAGG;gCAC+B,kBAAkB;IA0BpD;;;OAGG;8BAC6B,kBAAkB;IAuClD;;;OAGG;iCACgC,kBAAkB;IAmCrD;;;OAGG;uBACsB,OAAO;IAgBhC;;;OAGG;2BAC0B,OAAO;;;;;;;EAiGvC"}
|
|
@@ -2,37 +2,81 @@ import { redirect } from "react-router";
|
|
|
2
2
|
import { CivicAuth } from "@civic/auth/server";
|
|
3
3
|
import { ReactRouterCookieStorage } from "./cookies.js";
|
|
4
4
|
import { resolveAuthConfig } from "./config.js";
|
|
5
|
+
import { getProtocolFromRequest } from "../shared/lib/util.js";
|
|
6
|
+
/**
|
|
7
|
+
* Fields that are safe to expose to the frontend
|
|
8
|
+
*/
|
|
9
|
+
const WHITELISTED_FRONTEND_FIELDS = [
|
|
10
|
+
"clientId",
|
|
11
|
+
"oauthServer",
|
|
12
|
+
"callbackUrl",
|
|
13
|
+
"logoutCallbackUrl",
|
|
14
|
+
"loginUrl",
|
|
15
|
+
"baseUrl",
|
|
16
|
+
"logoutUrl",
|
|
17
|
+
];
|
|
5
18
|
/**
|
|
6
19
|
* Create auth route handlers for React Router - backend endpoints compatible with VanillaJS frontend integration
|
|
7
20
|
* These routes work similar to the Express example, using server-side CivicAuth SDK
|
|
8
21
|
*/
|
|
9
22
|
export function createRouteHandlers(configOverrides = {}) {
|
|
10
23
|
const config = resolveAuthConfig(configOverrides);
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
"
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
24
|
+
/**
|
|
25
|
+
* Gets a default base URL when no baseUrl is configured
|
|
26
|
+
* Uses request headers to determine protocol when possible
|
|
27
|
+
*/
|
|
28
|
+
const getDefaultBaseUrl = (request) => {
|
|
29
|
+
const protocol = getProtocolFromRequest(request);
|
|
30
|
+
const host = request ? new URL(request.url).host : "localhost:5191";
|
|
31
|
+
return `${protocol}//${host}`;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Resolves configuration with proper fallbacks based on request context
|
|
35
|
+
* @param request - The request object to extract host for fallbacks
|
|
36
|
+
* @returns Resolved configuration with all fallbacks applied
|
|
37
|
+
*/
|
|
38
|
+
const resolveConfigWithFallbacks = (request) => {
|
|
39
|
+
const host = request
|
|
40
|
+
? `${getProtocolFromRequest(request)}//${new URL(request.url).host}`
|
|
41
|
+
: (config.baseUrl ?? getDefaultBaseUrl(request));
|
|
42
|
+
return {
|
|
43
|
+
clientId: config.clientId,
|
|
44
|
+
oauthServer: config.oauthServer,
|
|
45
|
+
callbackUrl: config.callbackUrl ?? `${host}/auth/callback`,
|
|
46
|
+
logoutCallbackUrl: config.logoutCallbackUrl ?? `${host}/auth/logout`,
|
|
47
|
+
loginUrl: config.loginUrl ?? `${host}/auth/login`,
|
|
48
|
+
baseUrl: config.baseUrl ?? host,
|
|
49
|
+
loginSuccessUrl: config.loginSuccessUrl ?? `${host}`,
|
|
50
|
+
logoutUrl: config.logoutUrl ?? `${host}`,
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Gets the whitelisted frontend configuration with proper fallbacks
|
|
55
|
+
* Only exposes fields that are safe for frontend consumption
|
|
56
|
+
* @param request - The request object to extract host for fallbacks
|
|
57
|
+
* @returns The whitelisted configuration object with fallbacks applied
|
|
58
|
+
*/
|
|
59
|
+
const getWhitelistedFrontEndConfig = (request) => {
|
|
60
|
+
const resolved = resolveConfigWithFallbacks(request);
|
|
61
|
+
// Filter resolved config to only include whitelisted fields
|
|
62
|
+
return WHITELISTED_FRONTEND_FIELDS.reduce((acc, key) => {
|
|
63
|
+
acc[key] = resolved[key];
|
|
64
|
+
return acc;
|
|
65
|
+
}, {});
|
|
66
|
+
};
|
|
22
67
|
/**
|
|
23
68
|
* Helper to create CivicAuth instance for a request
|
|
24
69
|
*/
|
|
25
70
|
const createCivicAuth = (request) => {
|
|
26
|
-
const cookieStorage = new ReactRouterCookieStorage();
|
|
27
|
-
|
|
28
|
-
cookieStorage.setRequest(request);
|
|
29
|
-
const host = config.baseUrl ?? new URL(request.url).origin;
|
|
71
|
+
const cookieStorage = new ReactRouterCookieStorage(request);
|
|
72
|
+
const resolvedConfig = resolveConfigWithFallbacks(request);
|
|
30
73
|
const civicAuth = new CivicAuth(cookieStorage, {
|
|
31
|
-
clientId:
|
|
32
|
-
redirectUrl:
|
|
33
|
-
oauthServer:
|
|
34
|
-
postLogoutRedirectUrl:
|
|
35
|
-
loginSuccessUrl:
|
|
74
|
+
clientId: resolvedConfig.clientId,
|
|
75
|
+
redirectUrl: resolvedConfig.callbackUrl,
|
|
76
|
+
oauthServer: resolvedConfig.oauthServer,
|
|
77
|
+
postLogoutRedirectUrl: resolvedConfig.logoutCallbackUrl,
|
|
78
|
+
loginSuccessUrl: resolvedConfig.loginSuccessUrl,
|
|
79
|
+
loginUrl: resolvedConfig.loginUrl,
|
|
36
80
|
});
|
|
37
81
|
return {
|
|
38
82
|
civicAuth,
|
|
@@ -267,20 +311,24 @@ export function createRouteHandlers(configOverrides = {}) {
|
|
|
267
311
|
const { civicAuth } = createCivicAuth(request);
|
|
268
312
|
const isLoggedIn = await civicAuth.isLoggedIn();
|
|
269
313
|
const user = isLoggedIn ? await civicAuth.getUser() : null;
|
|
314
|
+
// Get the whitelisted config with proper fallbacks using the request
|
|
315
|
+
const configWithFallbacks = getWhitelistedFrontEndConfig(request);
|
|
270
316
|
return {
|
|
271
317
|
civic: {
|
|
272
318
|
user,
|
|
273
|
-
config:
|
|
319
|
+
config: configWithFallbacks,
|
|
274
320
|
isLoggedIn,
|
|
275
321
|
},
|
|
276
322
|
};
|
|
277
323
|
}
|
|
278
324
|
catch (error) {
|
|
279
325
|
console.error("[GETAUTHDATA_HANDLER] getAuthData error:", error);
|
|
326
|
+
// Even in error cases, provide config with fallbacks
|
|
327
|
+
const configWithFallbacks = getWhitelistedFrontEndConfig(request);
|
|
280
328
|
return {
|
|
281
329
|
civic: {
|
|
282
330
|
user: null,
|
|
283
|
-
config:
|
|
331
|
+
config: configWithFallbacks,
|
|
284
332
|
isLoggedIn: false,
|
|
285
333
|
},
|
|
286
334
|
};
|
|
@@ -315,9 +363,32 @@ export function createRouteHandlers(configOverrides = {}) {
|
|
|
315
363
|
}
|
|
316
364
|
};
|
|
317
365
|
};
|
|
366
|
+
/**
|
|
367
|
+
* Creates an action function that handles POST requests to auth routes
|
|
368
|
+
* @example
|
|
369
|
+
* // In your auth.$.tsx route file:
|
|
370
|
+
* export const action = createAuthAction();
|
|
371
|
+
*/
|
|
372
|
+
const createAuthAction = () => {
|
|
373
|
+
return async (args) => {
|
|
374
|
+
// Get the auth path from the URL
|
|
375
|
+
const authPath = args.params["*"];
|
|
376
|
+
// Route to the appropriate handler for POST requests
|
|
377
|
+
switch (authPath) {
|
|
378
|
+
case "refresh":
|
|
379
|
+
return handlers.refreshLoader(args); // Same logic for refresh regardless of GET/POST
|
|
380
|
+
default:
|
|
381
|
+
// Return 405 Method Not Allowed for unsupported POST paths
|
|
382
|
+
return new Response("Method Not Allowed", { status: 405 });
|
|
383
|
+
}
|
|
384
|
+
};
|
|
385
|
+
};
|
|
318
386
|
return {
|
|
319
387
|
...handlers,
|
|
320
388
|
createAuthLoader,
|
|
389
|
+
createAuthAction,
|
|
390
|
+
resolveConfigWithFallbacks,
|
|
391
|
+
getWhitelistedFrontEndConfig,
|
|
321
392
|
};
|
|
322
393
|
}
|
|
323
394
|
//# sourceMappingURL=routeHandler.js.map
|