@civic/auth 0.9.0 → 0.9.1-alpha.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.
- package/CHANGELOG.md +5 -0
- package/dist/lib/logger.d.ts +6 -0
- package/dist/lib/logger.d.ts.map +1 -1
- package/dist/lib/logger.js +7 -0
- package/dist/lib/logger.js.map +1 -1
- package/dist/nextjs/config.d.ts +2 -4
- package/dist/nextjs/config.d.ts.map +1 -1
- package/dist/nextjs/config.js +4 -57
- package/dist/nextjs/config.js.map +1 -1
- package/dist/react-router-7/components/UserButton.d.ts +13 -0
- package/dist/react-router-7/components/UserButton.d.ts.map +1 -0
- package/dist/react-router-7/components/UserButton.js +108 -0
- package/dist/react-router-7/components/UserButton.js.map +1 -0
- package/dist/react-router-7/components/UserButtonPresentation.d.ts +10 -0
- package/dist/react-router-7/components/UserButtonPresentation.d.ts.map +1 -0
- package/dist/react-router-7/components/UserButtonPresentation.js +19 -0
- package/dist/react-router-7/components/UserButtonPresentation.js.map +1 -0
- package/dist/react-router-7/config.d.ts +69 -0
- package/dist/react-router-7/config.d.ts.map +1 -0
- package/dist/react-router-7/config.js +88 -0
- package/dist/react-router-7/config.js.map +1 -0
- package/dist/react-router-7/cookies.d.ts +40 -0
- package/dist/react-router-7/cookies.d.ts.map +1 -0
- package/dist/react-router-7/cookies.js +125 -0
- package/dist/react-router-7/cookies.js.map +1 -0
- package/dist/react-router-7/index.d.ts +10 -0
- package/dist/react-router-7/index.d.ts.map +1 -0
- package/dist/react-router-7/index.js +12 -0
- package/dist/react-router-7/index.js.map +1 -0
- package/dist/react-router-7/routeHandler.d.ts +51 -0
- package/dist/react-router-7/routeHandler.d.ts.map +1 -0
- package/dist/react-router-7/routeHandler.js +323 -0
- package/dist/react-router-7/routeHandler.js.map +1 -0
- package/dist/react-router-7/useUser.d.ts +43 -0
- package/dist/react-router-7/useUser.d.ts.map +1 -0
- package/dist/react-router-7/useUser.js +92 -0
- package/dist/react-router-7/useUser.js.map +1 -0
- package/dist/reactjs/core/GlobalAuthManager.d.ts +2 -1
- package/dist/reactjs/core/GlobalAuthManager.d.ts.map +1 -1
- package/dist/reactjs/core/GlobalAuthManager.js +16 -2
- package/dist/reactjs/core/GlobalAuthManager.js.map +1 -1
- package/dist/server/ServerAuthenticationResolver.d.ts.map +1 -1
- package/dist/server/ServerAuthenticationResolver.js +4 -0
- package/dist/server/ServerAuthenticationResolver.js.map +1 -1
- package/dist/server/config.d.ts +11 -3
- package/dist/server/config.d.ts.map +1 -1
- package/dist/server/config.js.map +1 -1
- package/dist/server/login.d.ts.map +1 -1
- package/dist/server/login.js +5 -0
- package/dist/server/login.js.map +1 -1
- package/dist/server/logout.d.ts.map +1 -1
- package/dist/server/logout.js +5 -0
- package/dist/server/logout.js.map +1 -1
- package/dist/server/session.d.ts.map +1 -1
- package/dist/server/session.js +5 -0
- package/dist/server/session.js.map +1 -1
- package/dist/services/AuthenticationService.d.ts.map +1 -1
- package/dist/services/AuthenticationService.js +0 -5
- package/dist/services/AuthenticationService.js.map +1 -1
- package/dist/services/PKCE.d.ts.map +1 -1
- package/dist/services/PKCE.js +4 -1
- package/dist/services/PKCE.js.map +1 -1
- package/dist/shared/hooks/useCivicAuthConfig.d.ts +1 -1
- package/dist/shared/hooks/useCivicAuthConfig.d.ts.map +1 -1
- package/dist/shared/lib/AuthenticationRefresherImpl.d.ts.map +1 -1
- package/dist/shared/lib/AuthenticationRefresherImpl.js +4 -0
- package/dist/shared/lib/AuthenticationRefresherImpl.js.map +1 -1
- package/dist/shared/lib/cookieConfig.d.ts +46 -0
- package/dist/shared/lib/cookieConfig.d.ts.map +1 -0
- package/dist/shared/lib/cookieConfig.js +99 -0
- package/dist/shared/lib/cookieConfig.js.map +1 -0
- package/dist/shared/version.d.ts +1 -1
- package/dist/shared/version.d.ts.map +1 -1
- package/dist/shared/version.js +1 -1
- package/dist/shared/version.js.map +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/vanillajs/auth/CivicAuth.d.ts +1 -1
- package/dist/vanillajs/auth/CivicAuth.d.ts.map +1 -1
- package/dist/vanillajs/auth/CivicAuth.js +36 -9
- package/dist/vanillajs/auth/CivicAuth.js.map +1 -1
- package/dist/vanillajs/auth/config/ConfigProcessor.js +1 -1
- package/dist/vanillajs/auth/config/ConfigProcessor.js.map +1 -1
- package/dist/vanillajs/auth/handlers/MessageHandler.d.ts.map +1 -1
- package/dist/vanillajs/auth/handlers/MessageHandler.js +3 -0
- package/dist/vanillajs/auth/handlers/MessageHandler.js.map +1 -1
- package/dist/vanillajs/auth/types/AuthTypes.d.ts +31 -12
- package/dist/vanillajs/auth/types/AuthTypes.d.ts.map +1 -1
- package/dist/vanillajs/auth/types/AuthTypes.js.map +1 -1
- package/package.json +13 -1
|
@@ -0,0 +1 @@
|
|
|
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 * 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"]}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { CookieStorage } from "@civic/auth/server";
|
|
2
|
+
/**
|
|
3
|
+
* React Router implementation of the CookieStorage interface for Civic Auth
|
|
4
|
+
* Uses individual cookies for each token type instead of session storage
|
|
5
|
+
*/
|
|
6
|
+
export declare class ReactRouterCookieStorage extends CookieStorage {
|
|
7
|
+
private cookies;
|
|
8
|
+
private currentRequest;
|
|
9
|
+
private cookieHeaders;
|
|
10
|
+
constructor();
|
|
11
|
+
/**
|
|
12
|
+
* Set the current request context for reading cookies
|
|
13
|
+
*/
|
|
14
|
+
setRequest(request: Request): void;
|
|
15
|
+
/**
|
|
16
|
+
* Get cookie headers to be set in the response
|
|
17
|
+
*/
|
|
18
|
+
getCookieHeaders(): string[];
|
|
19
|
+
/**
|
|
20
|
+
* Get a value from a cookie
|
|
21
|
+
* Following React Router pattern: parse returns the object we serialized
|
|
22
|
+
*/
|
|
23
|
+
get(key: string): Promise<string | null>;
|
|
24
|
+
/**
|
|
25
|
+
* Set a value in a cookie
|
|
26
|
+
* Following React Router pattern: https://reactrouter.com/api/utils/createCookie
|
|
27
|
+
* cookie.serialize(object) creates the complete "Set-Cookie" header string
|
|
28
|
+
*/
|
|
29
|
+
set(key: string, value: string): Promise<void>;
|
|
30
|
+
/**
|
|
31
|
+
* Remove a value from a cookie
|
|
32
|
+
* Following React Router pattern: serialize with empty value and past expiration date
|
|
33
|
+
*/
|
|
34
|
+
remove(key: string): Promise<void>;
|
|
35
|
+
/**
|
|
36
|
+
* Delete a value from a cookie (alias for remove)
|
|
37
|
+
*/
|
|
38
|
+
delete(key: string): Promise<void>;
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=cookies.d.ts.map
|
|
@@ -0,0 +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;AAOnD;;;GAGG;AACH,qBAAa,wBAAyB,SAAQ,aAAa;IACzD,OAAO,CAAC,OAAO,CAAyB;IACxC,OAAO,CAAC,cAAc,CAAwB;IAC9C,OAAO,CAAC,aAAa,CAAgB;;IA4BrC;;OAEG;IACH,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IAKlC;;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"}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { createCookie } from "react-router";
|
|
2
|
+
import { CookieStorage } from "@civic/auth/server";
|
|
3
|
+
import { resolveAuthConfig } from "./config.js";
|
|
4
|
+
import { UserStorage } from "../shared/lib/types.js";
|
|
5
|
+
import { loggers } from "../lib/logger.js";
|
|
6
|
+
const logger = loggers.reactRouter.handlers.auth;
|
|
7
|
+
/**
|
|
8
|
+
* React Router implementation of the CookieStorage interface for Civic Auth
|
|
9
|
+
* Uses individual cookies for each token type instead of session storage
|
|
10
|
+
*/
|
|
11
|
+
export class ReactRouterCookieStorage extends CookieStorage {
|
|
12
|
+
cookies;
|
|
13
|
+
currentRequest = null;
|
|
14
|
+
cookieHeaders = [];
|
|
15
|
+
constructor() {
|
|
16
|
+
const config = resolveAuthConfig();
|
|
17
|
+
super({
|
|
18
|
+
secure: false,
|
|
19
|
+
});
|
|
20
|
+
// Create individual cookies for each token type
|
|
21
|
+
this.cookies = {};
|
|
22
|
+
// Create cookies for OAuth tokens
|
|
23
|
+
Object.entries(config.cookies.tokens).forEach(([tokenType, cookieConfig]) => {
|
|
24
|
+
this.cookies[tokenType] = createCookie(tokenType, {
|
|
25
|
+
...cookieConfig,
|
|
26
|
+
...this.cookies,
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
// Create cookie for user data
|
|
30
|
+
this.cookies[UserStorage.USER] = createCookie("user", {
|
|
31
|
+
...config.cookies.user,
|
|
32
|
+
...this.cookies,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
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
|
+
/**
|
|
43
|
+
* Get cookie headers to be set in the response
|
|
44
|
+
*/
|
|
45
|
+
getCookieHeaders() {
|
|
46
|
+
return this.cookieHeaders;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Get a value from a cookie
|
|
50
|
+
* Following React Router pattern: parse returns the object we serialized
|
|
51
|
+
*/
|
|
52
|
+
async get(key) {
|
|
53
|
+
if (!this.currentRequest) {
|
|
54
|
+
logger.warn("No request context set for cookie reading");
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
const cookie = this.cookies[key];
|
|
58
|
+
if (!cookie) {
|
|
59
|
+
logger.warn(`No cookie configured for key: ${key}`);
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
const cookieHeader = this.currentRequest.headers.get("Cookie");
|
|
64
|
+
// Parse returns the object we serialized: { value: "actual_value" }
|
|
65
|
+
const parsedObject = await cookie.parse(cookieHeader);
|
|
66
|
+
// Extract the actual value from the object
|
|
67
|
+
const actualValue = parsedObject?.value || null;
|
|
68
|
+
return actualValue;
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
console.error(`Error reading cookie ${key}:`, error);
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Set a value in a cookie
|
|
77
|
+
* Following React Router pattern: https://reactrouter.com/api/utils/createCookie
|
|
78
|
+
* cookie.serialize(object) creates the complete "Set-Cookie" header string
|
|
79
|
+
*/
|
|
80
|
+
async set(key, value) {
|
|
81
|
+
const cookie = this.cookies[key];
|
|
82
|
+
if (!cookie) {
|
|
83
|
+
logger.warn(`No cookie configured for key: ${key}`);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
try {
|
|
87
|
+
// Following the React Router pattern: serialize an object, not a raw string
|
|
88
|
+
// This matches the docs example: cookie.serialize({ showBanner: true })
|
|
89
|
+
const cookieValue = { value };
|
|
90
|
+
const serializedCookie = await cookie.serialize(cookieValue);
|
|
91
|
+
this.cookieHeaders.push(serializedCookie);
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
console.error(`Error setting cookie ${key}:`, error);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Remove a value from a cookie
|
|
99
|
+
* Following React Router pattern: serialize with empty value and past expiration date
|
|
100
|
+
*/
|
|
101
|
+
async remove(key) {
|
|
102
|
+
const cookie = this.cookies[key];
|
|
103
|
+
if (!cookie) {
|
|
104
|
+
logger.warn(`No cookie configured for key: ${key}`);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
try {
|
|
108
|
+
// Following React Router pattern: serialize empty object with immediate expiration to delete
|
|
109
|
+
const serializedCookie = await cookie.serialize({ value: "" }, {
|
|
110
|
+
expires: new Date(0),
|
|
111
|
+
});
|
|
112
|
+
this.cookieHeaders.push(serializedCookie);
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
console.error(`Error removing cookie ${key}:`, error);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Delete a value from a cookie (alias for remove)
|
|
120
|
+
*/
|
|
121
|
+
async delete(key) {
|
|
122
|
+
await this.remove(key);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=cookies.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cookies.js","sourceRoot":"","sources":["../../src/react-router-7/cookies.ts"],"names":[],"mappings":"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","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"]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React Router authentication modules for Civic Auth
|
|
3
|
+
*/
|
|
4
|
+
export { createRouteHandlers } from "./routeHandler.js";
|
|
5
|
+
export { useUser } from "./useUser.js";
|
|
6
|
+
export { resolveAuthConfig, createCivicAuthConfig, type AuthConfig, type AuthConfigWithDefaults, type LoggingConfig, type CookiesConfigObject, } from "./config.js";
|
|
7
|
+
export { ReactRouterCookieStorage } from "./cookies.js";
|
|
8
|
+
export { UserButton } from "./components/UserButton.js";
|
|
9
|
+
export type { AuthData } from "./useUser.js";
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react-router-7/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAGxD,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAGvC,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,KAAK,UAAU,EACf,KAAK,sBAAsB,EAC3B,KAAK,aAAa,EAClB,KAAK,mBAAmB,GACzB,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAC;AACxD,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAGxD,YAAY,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React Router authentication modules for Civic Auth
|
|
3
|
+
*/
|
|
4
|
+
// Export all components from the React Router package
|
|
5
|
+
export { createRouteHandlers } from "./routeHandler.js";
|
|
6
|
+
// Export hooks - simplified for React Router SSR patterns
|
|
7
|
+
export { useUser } from "./useUser.js";
|
|
8
|
+
// Export configuration and helpers
|
|
9
|
+
export { resolveAuthConfig, createCivicAuthConfig, } from "./config.js";
|
|
10
|
+
export { ReactRouterCookieStorage } from "./cookies.js";
|
|
11
|
+
export { UserButton } from "./components/UserButton.js";
|
|
12
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/react-router-7/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,sDAAsD;AACtD,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAExD,0DAA0D;AAC1D,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC,mCAAmC;AACnC,OAAO,EACL,iBAAiB,EACjB,qBAAqB,GAKtB,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAC;AACxD,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC","sourcesContent":["/**\n * React Router authentication modules for Civic Auth\n */\n\n// Export all components from the React Router package\nexport { createRouteHandlers } from \"./routeHandler.js\";\n\n// Export hooks - simplified for React Router SSR patterns\nexport { useUser } from \"./useUser.js\";\n\n// Export configuration and helpers\nexport {\n resolveAuthConfig,\n createCivicAuthConfig,\n type AuthConfig,\n type AuthConfigWithDefaults,\n type LoggingConfig,\n type CookiesConfigObject,\n} from \"./config.js\";\n\nexport { ReactRouterCookieStorage } from \"./cookies.js\";\nexport { UserButton } from \"./components/UserButton.js\";\n\n// Types - simplified for React Router patterns\nexport type { AuthData } from \"./useUser.js\";\n"]}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { type LoaderFunctionArgs } from "react-router";
|
|
2
|
+
import type { AuthConfig } from "./config.js";
|
|
3
|
+
/**
|
|
4
|
+
* Create auth route handlers for React Router - backend endpoints compatible with VanillaJS frontend integration
|
|
5
|
+
* These routes work similar to the Express example, using server-side CivicAuth SDK
|
|
6
|
+
*/
|
|
7
|
+
export declare function createRouteHandlers(configOverrides?: Partial<AuthConfig>): {
|
|
8
|
+
createAuthLoader: () => (args: LoaderFunctionArgs) => Promise<Response>;
|
|
9
|
+
/**
|
|
10
|
+
* Login loader - backend OAuth login initiation endpoint
|
|
11
|
+
* Uses CivicAuth.buildLoginUrl() like Express example
|
|
12
|
+
*/
|
|
13
|
+
loginLoader: ({ request }: LoaderFunctionArgs) => Promise<Response>;
|
|
14
|
+
/**
|
|
15
|
+
* Callback loader - backend OAuth callback endpoint
|
|
16
|
+
* Uses CivicAuth.handleCallback() like Express example
|
|
17
|
+
*/
|
|
18
|
+
callbackLoader: ({ request }: LoaderFunctionArgs) => Promise<Response>;
|
|
19
|
+
/**
|
|
20
|
+
* Logout loader - backend logout endpoint
|
|
21
|
+
* Uses CivicAuth.buildLogoutRedirectUrl() and clearTokens() like Express example
|
|
22
|
+
*/
|
|
23
|
+
logoutLoader: ({ request }: LoaderFunctionArgs) => Promise<Response>;
|
|
24
|
+
/**
|
|
25
|
+
* User endpoint - returns current user data as JSON
|
|
26
|
+
* Uses CivicAuth.isLoggedIn() and getUser() like Express example
|
|
27
|
+
*/
|
|
28
|
+
userLoader: ({ request }: LoaderFunctionArgs) => Promise<Response>;
|
|
29
|
+
/**
|
|
30
|
+
* Refresh endpoint - refreshes access tokens
|
|
31
|
+
* Uses CivicAuth.refreshTokens() like Express example
|
|
32
|
+
*/
|
|
33
|
+
refreshLoader: ({ request }: LoaderFunctionArgs) => Promise<Response>;
|
|
34
|
+
/**
|
|
35
|
+
* Get user data from session (for SSR)
|
|
36
|
+
* Uses CivicAuth.isLoggedIn() and getUser() like Express example
|
|
37
|
+
*/
|
|
38
|
+
getUser: (request: Request) => Promise<import("../types.js").BaseUser | null>;
|
|
39
|
+
/**
|
|
40
|
+
* Get auth data including user and config (for SSR)
|
|
41
|
+
* Returns user data, config, and other auth-related data needed by the app under a civic key
|
|
42
|
+
*/
|
|
43
|
+
getAuthData: (request: Request) => Promise<{
|
|
44
|
+
civic: {
|
|
45
|
+
user: import("../types.js").BaseUser | null;
|
|
46
|
+
config: Record<string, string>;
|
|
47
|
+
isLoggedIn: boolean;
|
|
48
|
+
};
|
|
49
|
+
}>;
|
|
50
|
+
};
|
|
51
|
+
//# sourceMappingURL=routeHandler.d.ts.map
|
|
@@ -0,0 +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,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,eAAe,GAAE,OAAO,CAAC,UAAU,CAAM;mCA8WrD,kBAAkB;IA7QtC;;;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;;;;;;;EAkEvC"}
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
import { redirect } from "react-router";
|
|
2
|
+
import { CivicAuth } from "@civic/auth/server";
|
|
3
|
+
import { ReactRouterCookieStorage } from "./cookies.js";
|
|
4
|
+
import { resolveAuthConfig } from "./config.js";
|
|
5
|
+
/**
|
|
6
|
+
* Create auth route handlers for React Router - backend endpoints compatible with VanillaJS frontend integration
|
|
7
|
+
* These routes work similar to the Express example, using server-side CivicAuth SDK
|
|
8
|
+
*/
|
|
9
|
+
export function createRouteHandlers(configOverrides = {}) {
|
|
10
|
+
const config = resolveAuthConfig(configOverrides);
|
|
11
|
+
const whitelistedFrontEndConfig = [
|
|
12
|
+
"clientId",
|
|
13
|
+
"oauthServer",
|
|
14
|
+
"callbackUrl",
|
|
15
|
+
"logoutCallbackUrl",
|
|
16
|
+
"loginUrl",
|
|
17
|
+
"baseUrl",
|
|
18
|
+
].reduce((acc, key) => {
|
|
19
|
+
acc[key] = config[key];
|
|
20
|
+
return acc;
|
|
21
|
+
}, {});
|
|
22
|
+
/**
|
|
23
|
+
* Helper to create CivicAuth instance for a request
|
|
24
|
+
*/
|
|
25
|
+
const createCivicAuth = (request) => {
|
|
26
|
+
const cookieStorage = new ReactRouterCookieStorage();
|
|
27
|
+
// Set the request context for reading cookies
|
|
28
|
+
cookieStorage.setRequest(request);
|
|
29
|
+
const host = config.baseUrl ?? new URL(request.url).origin;
|
|
30
|
+
const civicAuth = new CivicAuth(cookieStorage, {
|
|
31
|
+
clientId: config.clientId,
|
|
32
|
+
redirectUrl: config.callbackUrl ?? `${host}/auth/callback`,
|
|
33
|
+
oauthServer: config.oauthServer,
|
|
34
|
+
postLogoutRedirectUrl: config.logoutCallbackUrl ?? `${host}/auth/logout`,
|
|
35
|
+
loginSuccessUrl: `${host}/auth/callback`,
|
|
36
|
+
});
|
|
37
|
+
return {
|
|
38
|
+
civicAuth,
|
|
39
|
+
// Use the original cookieStorage instance that we properly configured with setRequest()
|
|
40
|
+
cookieStorage,
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Helper to create response with cookie headers
|
|
45
|
+
* Following React Router pattern: add serialized cookies as "Set-Cookie" headers
|
|
46
|
+
*/
|
|
47
|
+
const createResponseWithCookies = (body, init, cookieStorage) => {
|
|
48
|
+
const headers = new Headers(init.headers);
|
|
49
|
+
// Add cookie headers from storage - each one is a complete "Set-Cookie" header from cookie.serialize()
|
|
50
|
+
const cookieHeaders = cookieStorage.getCookieHeaders();
|
|
51
|
+
cookieHeaders.forEach((cookieHeader) => {
|
|
52
|
+
headers.append("Set-Cookie", cookieHeader);
|
|
53
|
+
});
|
|
54
|
+
return new Response(body, {
|
|
55
|
+
...init,
|
|
56
|
+
headers,
|
|
57
|
+
});
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Helper to create redirect with cookie headers
|
|
61
|
+
* Following React Router pattern: add serialized cookies as "Set-Cookie" headers
|
|
62
|
+
*/
|
|
63
|
+
const redirectWithCookies = (url, cookieStorage, init) => {
|
|
64
|
+
const headers = new Headers(init?.headers);
|
|
65
|
+
// Add cookie headers from storage - each one is a complete "Set-Cookie" header from cookie.serialize()
|
|
66
|
+
const cookieHeaders = cookieStorage.getCookieHeaders();
|
|
67
|
+
cookieHeaders.forEach((cookieHeader) => {
|
|
68
|
+
headers.append("Set-Cookie", cookieHeader);
|
|
69
|
+
});
|
|
70
|
+
// Set Location header for proper redirect
|
|
71
|
+
headers.set("Location", url);
|
|
72
|
+
return new Response(null, {
|
|
73
|
+
...init,
|
|
74
|
+
status: 302,
|
|
75
|
+
headers,
|
|
76
|
+
});
|
|
77
|
+
};
|
|
78
|
+
const handlers = {
|
|
79
|
+
/**
|
|
80
|
+
* Login loader - backend OAuth login initiation endpoint
|
|
81
|
+
* Uses CivicAuth.buildLoginUrl() like Express example
|
|
82
|
+
*/
|
|
83
|
+
loginLoader: async ({ request }) => {
|
|
84
|
+
const incomingUrl = new URL(request.url);
|
|
85
|
+
const frontendState = incomingUrl.searchParams.get("state");
|
|
86
|
+
// const returnTo = incomingUrl.searchParams.get("returnTo") || "/";
|
|
87
|
+
try {
|
|
88
|
+
const { civicAuth, cookieStorage } = createCivicAuth(request);
|
|
89
|
+
const url = await civicAuth.buildLoginUrl({
|
|
90
|
+
state: frontendState || undefined,
|
|
91
|
+
});
|
|
92
|
+
const response = redirectWithCookies(url.toString(), cookieStorage);
|
|
93
|
+
return response;
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
console.error("[LOGIN_HANDLER] Backend login error:", error);
|
|
97
|
+
return redirect("/?error=login_failed");
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
/**
|
|
101
|
+
* Callback loader - backend OAuth callback endpoint
|
|
102
|
+
* Uses CivicAuth.handleCallback() like Express example
|
|
103
|
+
*/
|
|
104
|
+
callbackLoader: async ({ request }) => {
|
|
105
|
+
try {
|
|
106
|
+
const url = new URL(request.url);
|
|
107
|
+
const code = url.searchParams.get("code");
|
|
108
|
+
const state = url.searchParams.get("state");
|
|
109
|
+
const error = url.searchParams.get("error");
|
|
110
|
+
if (error) {
|
|
111
|
+
console.error("OAuth error in callback:", error);
|
|
112
|
+
return redirect("/?error=oauth_error");
|
|
113
|
+
}
|
|
114
|
+
if (!code || !state) {
|
|
115
|
+
throw new Error("Missing code or state parameter");
|
|
116
|
+
}
|
|
117
|
+
const { civicAuth, cookieStorage } = createCivicAuth(request);
|
|
118
|
+
// Convert React Router request to the format expected by handleCallback
|
|
119
|
+
const handleCallbackRequest = {
|
|
120
|
+
headers: Object.fromEntries(request.headers.entries()),
|
|
121
|
+
};
|
|
122
|
+
// For non-iframe requests, use the original handleCallback logic
|
|
123
|
+
const result = await civicAuth.handleCallback({
|
|
124
|
+
code,
|
|
125
|
+
state,
|
|
126
|
+
req: handleCallbackRequest,
|
|
127
|
+
});
|
|
128
|
+
if (result.redirectTo) {
|
|
129
|
+
return redirectWithCookies(result.redirectTo, cookieStorage);
|
|
130
|
+
}
|
|
131
|
+
if (result.content) {
|
|
132
|
+
// Handle both string content and object content
|
|
133
|
+
if (typeof result.content === "string") {
|
|
134
|
+
return createResponseWithCookies(result.content, {
|
|
135
|
+
status: 200,
|
|
136
|
+
headers: {
|
|
137
|
+
"Content-Type": "text/html",
|
|
138
|
+
},
|
|
139
|
+
}, cookieStorage);
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
// Object content (JSON response)
|
|
143
|
+
return createResponseWithCookies(JSON.stringify(result.content), {
|
|
144
|
+
status: 200,
|
|
145
|
+
headers: {
|
|
146
|
+
"Content-Type": "application/json",
|
|
147
|
+
},
|
|
148
|
+
}, cookieStorage);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
// Fallback redirect
|
|
152
|
+
return redirectWithCookies("/", cookieStorage);
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
console.error("[CALLBACK_HANDLER] OAuth callback error:", error);
|
|
156
|
+
return redirect("/?error=callback_failed");
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
/**
|
|
160
|
+
* Logout loader - backend logout endpoint
|
|
161
|
+
* Uses CivicAuth.buildLogoutRedirectUrl() and clearTokens() like Express example
|
|
162
|
+
*/
|
|
163
|
+
logoutLoader: async ({ request }) => {
|
|
164
|
+
try {
|
|
165
|
+
const { civicAuth, cookieStorage } = createCivicAuth(request);
|
|
166
|
+
const logoutUrl = await civicAuth.buildLogoutRedirectUrl();
|
|
167
|
+
await civicAuth.clearTokens();
|
|
168
|
+
const url = new URL(logoutUrl.toString());
|
|
169
|
+
// Remove the state parameter to avoid it showing up in the frontend URL
|
|
170
|
+
url.searchParams.delete("state");
|
|
171
|
+
return redirectWithCookies(url.toString(), cookieStorage);
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
console.error("[LOGOUT_HANDLER] Logout error:", error);
|
|
175
|
+
// If logout URL generation fails, clear tokens and redirect to home (Express pattern)
|
|
176
|
+
try {
|
|
177
|
+
const { civicAuth, cookieStorage } = createCivicAuth(request);
|
|
178
|
+
await civicAuth.clearTokens();
|
|
179
|
+
return redirectWithCookies("/", cookieStorage);
|
|
180
|
+
}
|
|
181
|
+
catch (clearError) {
|
|
182
|
+
console.error("[LOGOUT_HANDLER] Failed to clear tokens:", clearError);
|
|
183
|
+
return redirect("/");
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
},
|
|
187
|
+
/**
|
|
188
|
+
* User endpoint - returns current user data as JSON
|
|
189
|
+
* Uses CivicAuth.isLoggedIn() and getUser() like Express example
|
|
190
|
+
*/
|
|
191
|
+
userLoader: async ({ request }) => {
|
|
192
|
+
try {
|
|
193
|
+
const { civicAuth, cookieStorage } = createCivicAuth(request);
|
|
194
|
+
const isLoggedIn = await civicAuth.isLoggedIn();
|
|
195
|
+
if (!isLoggedIn) {
|
|
196
|
+
return createResponseWithCookies(JSON.stringify({ error: "Not authenticated" }), {
|
|
197
|
+
status: 401,
|
|
198
|
+
headers: { "Content-Type": "application/json" },
|
|
199
|
+
}, cookieStorage);
|
|
200
|
+
}
|
|
201
|
+
const user = await civicAuth.getUser();
|
|
202
|
+
return createResponseWithCookies(JSON.stringify({ user }), {
|
|
203
|
+
status: 200,
|
|
204
|
+
headers: { "Content-Type": "application/json" },
|
|
205
|
+
}, cookieStorage);
|
|
206
|
+
}
|
|
207
|
+
catch (error) {
|
|
208
|
+
console.error("[USER_HANDLER] User endpoint error:", error);
|
|
209
|
+
return new Response(JSON.stringify({ error: "Internal server error" }), {
|
|
210
|
+
status: 500,
|
|
211
|
+
headers: { "Content-Type": "application/json" },
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
/**
|
|
216
|
+
* Refresh endpoint - refreshes access tokens
|
|
217
|
+
* Uses CivicAuth.refreshTokens() like Express example
|
|
218
|
+
*/
|
|
219
|
+
refreshLoader: async ({ request }) => {
|
|
220
|
+
try {
|
|
221
|
+
const { civicAuth, cookieStorage } = createCivicAuth(request);
|
|
222
|
+
const isLoggedIn = await civicAuth.isLoggedIn();
|
|
223
|
+
if (!isLoggedIn) {
|
|
224
|
+
return createResponseWithCookies(JSON.stringify({ error: "Not authenticated" }), {
|
|
225
|
+
status: 401,
|
|
226
|
+
headers: { "Content-Type": "application/json" },
|
|
227
|
+
}, cookieStorage);
|
|
228
|
+
}
|
|
229
|
+
await civicAuth.refreshTokens();
|
|
230
|
+
return createResponseWithCookies(JSON.stringify({ success: true, message: "Tokens refreshed" }), {
|
|
231
|
+
status: 200,
|
|
232
|
+
headers: { "Content-Type": "application/json" },
|
|
233
|
+
}, cookieStorage);
|
|
234
|
+
}
|
|
235
|
+
catch (error) {
|
|
236
|
+
console.error("[REFRESH_HANDLER] Token refresh error:", error);
|
|
237
|
+
return new Response(JSON.stringify({ error: "Token refresh failed" }), {
|
|
238
|
+
status: 500,
|
|
239
|
+
headers: { "Content-Type": "application/json" },
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
},
|
|
243
|
+
/**
|
|
244
|
+
* Get user data from session (for SSR)
|
|
245
|
+
* Uses CivicAuth.isLoggedIn() and getUser() like Express example
|
|
246
|
+
*/
|
|
247
|
+
getUser: async (request) => {
|
|
248
|
+
try {
|
|
249
|
+
const { civicAuth } = createCivicAuth(request);
|
|
250
|
+
const isLoggedIn = await civicAuth.isLoggedIn();
|
|
251
|
+
if (!isLoggedIn) {
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
return await civicAuth.getUser();
|
|
255
|
+
}
|
|
256
|
+
catch (error) {
|
|
257
|
+
console.error("[GETUSER_HANDLER] getUser error:", error);
|
|
258
|
+
return null;
|
|
259
|
+
}
|
|
260
|
+
},
|
|
261
|
+
/**
|
|
262
|
+
* Get auth data including user and config (for SSR)
|
|
263
|
+
* Returns user data, config, and other auth-related data needed by the app under a civic key
|
|
264
|
+
*/
|
|
265
|
+
getAuthData: async (request) => {
|
|
266
|
+
try {
|
|
267
|
+
const { civicAuth } = createCivicAuth(request);
|
|
268
|
+
const isLoggedIn = await civicAuth.isLoggedIn();
|
|
269
|
+
const user = isLoggedIn ? await civicAuth.getUser() : null;
|
|
270
|
+
return {
|
|
271
|
+
civic: {
|
|
272
|
+
user,
|
|
273
|
+
config: whitelistedFrontEndConfig,
|
|
274
|
+
isLoggedIn,
|
|
275
|
+
},
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
catch (error) {
|
|
279
|
+
console.error("[GETAUTHDATA_HANDLER] getAuthData error:", error);
|
|
280
|
+
return {
|
|
281
|
+
civic: {
|
|
282
|
+
user: null,
|
|
283
|
+
config: whitelistedFrontEndConfig,
|
|
284
|
+
isLoggedIn: false,
|
|
285
|
+
},
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
},
|
|
289
|
+
};
|
|
290
|
+
/**
|
|
291
|
+
* Creates a loader function that handles all auth routes
|
|
292
|
+
* @example
|
|
293
|
+
* // In your auth.$.tsx route file:
|
|
294
|
+
* export const loader = createAuthLoader();
|
|
295
|
+
*/
|
|
296
|
+
const createAuthLoader = () => {
|
|
297
|
+
return async (args) => {
|
|
298
|
+
// Get the auth path from the URL
|
|
299
|
+
const authPath = args.params["*"];
|
|
300
|
+
// Route to the appropriate handler
|
|
301
|
+
switch (authPath) {
|
|
302
|
+
case "login":
|
|
303
|
+
return handlers.loginLoader(args);
|
|
304
|
+
case "callback":
|
|
305
|
+
return handlers.callbackLoader(args);
|
|
306
|
+
case "refresh":
|
|
307
|
+
return handlers.refreshLoader(args);
|
|
308
|
+
case "logout":
|
|
309
|
+
return handlers.logoutLoader(args);
|
|
310
|
+
case "user":
|
|
311
|
+
return handlers.userLoader(args);
|
|
312
|
+
default:
|
|
313
|
+
// Return 404 for unknown auth paths
|
|
314
|
+
return new Response("Not Found", { status: 404 });
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
};
|
|
318
|
+
return {
|
|
319
|
+
...handlers,
|
|
320
|
+
createAuthLoader,
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
//# sourceMappingURL=routeHandler.js.map
|