@tern-secure/nextjs 4.0.0 → 4.2.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.
Files changed (132) hide show
  1. package/dist/cjs/app-router/client/TernSecureProvider.js +17 -2
  2. package/dist/cjs/app-router/client/TernSecureProvider.js.map +1 -1
  3. package/dist/cjs/app-router/client/actions.js +49 -49
  4. package/dist/cjs/app-router/client/actions.js.map +1 -1
  5. package/dist/cjs/app-router/route-handler/internal-route.js +17 -2
  6. package/dist/cjs/app-router/route-handler/internal-route.js.map +1 -1
  7. package/dist/cjs/app-router/server/auth.js +42 -28
  8. package/dist/cjs/app-router/server/auth.js.map +1 -1
  9. package/dist/cjs/app-router/server/edge-session.js +80 -0
  10. package/dist/cjs/app-router/server/edge-session.js.map +1 -0
  11. package/dist/cjs/app-router/server/index.js +4 -0
  12. package/dist/cjs/app-router/server/index.js.map +1 -1
  13. package/dist/cjs/app-router/server/jwt.js +141 -0
  14. package/dist/cjs/app-router/server/jwt.js.map +1 -0
  15. package/dist/cjs/app-router/server/sessionTernSecure.js +14 -9
  16. package/dist/cjs/app-router/server/sessionTernSecure.js.map +1 -1
  17. package/dist/cjs/app-router/server/ternSecureMiddleware.js +134 -13
  18. package/dist/cjs/app-router/server/ternSecureMiddleware.js.map +1 -1
  19. package/dist/cjs/boundary/TernSecureClientProvider.js +163 -40
  20. package/dist/cjs/boundary/TernSecureClientProvider.js.map +1 -1
  21. package/dist/cjs/boundary/TernSecureCtx.js.map +1 -1
  22. package/dist/cjs/boundary/hooks/useAuth.js +7 -8
  23. package/dist/cjs/boundary/hooks/useAuth.js.map +1 -1
  24. package/dist/cjs/components/sign-in.js +136 -45
  25. package/dist/cjs/components/sign-in.js.map +1 -1
  26. package/dist/cjs/components/sign-out-button.js +10 -1
  27. package/dist/cjs/components/sign-out-button.js.map +1 -1
  28. package/dist/cjs/components/sign-out.js +12 -3
  29. package/dist/cjs/components/sign-out.js.map +1 -1
  30. package/dist/cjs/components/sign-up.js +10 -5
  31. package/dist/cjs/components/sign-up.js.map +1 -1
  32. package/dist/cjs/errors.js +232 -5
  33. package/dist/cjs/errors.js.map +1 -1
  34. package/dist/cjs/index.js +0 -3
  35. package/dist/cjs/index.js.map +1 -1
  36. package/dist/cjs/types.js +14 -0
  37. package/dist/cjs/types.js.map +1 -1
  38. package/dist/cjs/utils/construct.js +50 -18
  39. package/dist/cjs/utils/construct.js.map +1 -1
  40. package/dist/cjs/utils/redirect.js +57 -0
  41. package/dist/cjs/utils/redirect.js.map +1 -0
  42. package/dist/esm/app-router/client/TernSecureProvider.js +17 -2
  43. package/dist/esm/app-router/client/TernSecureProvider.js.map +1 -1
  44. package/dist/esm/app-router/client/actions.js +59 -51
  45. package/dist/esm/app-router/client/actions.js.map +1 -1
  46. package/dist/esm/app-router/route-handler/internal-route.js +13 -1
  47. package/dist/esm/app-router/route-handler/internal-route.js.map +1 -1
  48. package/dist/esm/app-router/server/auth.js +40 -28
  49. package/dist/esm/app-router/server/auth.js.map +1 -1
  50. package/dist/esm/app-router/server/edge-session.js +56 -0
  51. package/dist/esm/app-router/server/edge-session.js.map +1 -0
  52. package/dist/esm/app-router/server/index.js +4 -2
  53. package/dist/esm/app-router/server/index.js.map +1 -1
  54. package/dist/esm/app-router/server/jwt.js +117 -0
  55. package/dist/esm/app-router/server/jwt.js.map +1 -0
  56. package/dist/esm/app-router/server/sessionTernSecure.js +14 -9
  57. package/dist/esm/app-router/server/sessionTernSecure.js.map +1 -1
  58. package/dist/esm/app-router/server/ternSecureMiddleware.js +132 -13
  59. package/dist/esm/app-router/server/ternSecureMiddleware.js.map +1 -1
  60. package/dist/esm/boundary/TernSecureClientProvider.js +164 -41
  61. package/dist/esm/boundary/TernSecureClientProvider.js.map +1 -1
  62. package/dist/esm/boundary/TernSecureCtx.js.map +1 -1
  63. package/dist/esm/boundary/hooks/useAuth.js +7 -8
  64. package/dist/esm/boundary/hooks/useAuth.js.map +1 -1
  65. package/dist/esm/components/sign-in.js +137 -46
  66. package/dist/esm/components/sign-in.js.map +1 -1
  67. package/dist/esm/components/sign-out-button.js +11 -2
  68. package/dist/esm/components/sign-out-button.js.map +1 -1
  69. package/dist/esm/components/sign-out.js +13 -4
  70. package/dist/esm/components/sign-out.js.map +1 -1
  71. package/dist/esm/components/sign-up.js +10 -5
  72. package/dist/esm/components/sign-up.js.map +1 -1
  73. package/dist/esm/errors.js +228 -4
  74. package/dist/esm/errors.js.map +1 -1
  75. package/dist/esm/index.js +0 -2
  76. package/dist/esm/index.js.map +1 -1
  77. package/dist/esm/types.js +6 -0
  78. package/dist/esm/types.js.map +1 -1
  79. package/dist/esm/utils/construct.js +46 -17
  80. package/dist/esm/utils/construct.js.map +1 -1
  81. package/dist/esm/utils/redirect.js +32 -0
  82. package/dist/esm/utils/redirect.js.map +1 -0
  83. package/dist/types/app-router/client/TernSecureProvider.d.ts +14 -3
  84. package/dist/types/app-router/client/TernSecureProvider.d.ts.map +1 -1
  85. package/dist/types/app-router/client/actions.d.ts +23 -21
  86. package/dist/types/app-router/client/actions.d.ts.map +1 -1
  87. package/dist/types/app-router/route-handler/internal-route.d.ts +3 -0
  88. package/dist/types/app-router/route-handler/internal-route.d.ts.map +1 -1
  89. package/dist/types/app-router/server/auth.d.ts +13 -1
  90. package/dist/types/app-router/server/auth.d.ts.map +1 -1
  91. package/dist/types/app-router/server/edge-session.d.ts +15 -0
  92. package/dist/types/app-router/server/edge-session.d.ts.map +1 -0
  93. package/dist/types/app-router/server/index.d.ts +3 -2
  94. package/dist/types/app-router/server/index.d.ts.map +1 -1
  95. package/dist/types/app-router/server/jwt.d.ts +20 -0
  96. package/dist/types/app-router/server/jwt.d.ts.map +1 -0
  97. package/dist/types/app-router/server/sessionTernSecure.d.ts +4 -1
  98. package/dist/types/app-router/server/sessionTernSecure.d.ts.map +1 -1
  99. package/dist/types/app-router/server/ternSecureMiddleware.d.ts +17 -4
  100. package/dist/types/app-router/server/ternSecureMiddleware.d.ts.map +1 -1
  101. package/dist/types/boundary/TernSecureClientProvider.d.ts +17 -1
  102. package/dist/types/boundary/TernSecureClientProvider.d.ts.map +1 -1
  103. package/dist/types/boundary/TernSecureCtx.d.ts +3 -1
  104. package/dist/types/boundary/TernSecureCtx.d.ts.map +1 -1
  105. package/dist/types/boundary/hooks/useAuth.d.ts +4 -1
  106. package/dist/types/boundary/hooks/useAuth.d.ts.map +1 -1
  107. package/dist/types/components/sign-in.d.ts +1 -2
  108. package/dist/types/components/sign-in.d.ts.map +1 -1
  109. package/dist/types/components/sign-out-button.d.ts +2 -1
  110. package/dist/types/components/sign-out-button.d.ts.map +1 -1
  111. package/dist/types/components/sign-out.d.ts +2 -1
  112. package/dist/types/components/sign-out.d.ts.map +1 -1
  113. package/dist/types/components/sign-up.d.ts.map +1 -1
  114. package/dist/types/components/ui/alert.d.ts +1 -1
  115. package/dist/types/components/ui/button.d.ts +1 -1
  116. package/dist/types/errors.d.ts +36 -2
  117. package/dist/types/errors.d.ts.map +1 -1
  118. package/dist/types/index.d.ts +0 -1
  119. package/dist/types/index.d.ts.map +1 -1
  120. package/dist/types/types.d.ts +35 -0
  121. package/dist/types/types.d.ts.map +1 -1
  122. package/dist/types/utils/construct.d.ts +20 -4
  123. package/dist/types/utils/construct.d.ts.map +1 -1
  124. package/dist/types/utils/redirect.d.ts +9 -0
  125. package/dist/types/utils/redirect.d.ts.map +1 -0
  126. package/package.json +7 -6
  127. package/dist/cjs/boundary/hooks/useUser.js +0 -44
  128. package/dist/cjs/boundary/hooks/useUser.js.map +0 -1
  129. package/dist/esm/boundary/hooks/useUser.js +0 -20
  130. package/dist/esm/boundary/hooks/useUser.js.map +0 -1
  131. package/dist/types/boundary/hooks/useUser.d.ts +0 -7
  132. package/dist/types/boundary/hooks/useUser.d.ts.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/app-router/server/sessionTernSecure.ts"],"sourcesContent":["'use server'\n\nimport { cookies } from 'next/headers';\nimport { adminTernSecureAuth as adminAuth } from '../../utils/admin-init';\n\ninterface FirebaseAuthError extends Error {\n code?: string;\n}\n\nexport interface User {\n uid: string | null;\n email: string | null;\n }\n\nexport interface Session {\n user: User | null;\n token: string | null;\n error: Error | null;\n}\n\nexport async function createSessionCookie(idToken: string) {\n try {\n const expiresIn = 60 * 60 * 24 * 5 * 1000;\n const sessionCookie = await adminAuth.createSessionCookie(idToken, { expiresIn });\n\n const cookieStore = await cookies();\n cookieStore.set('_session_cookie', sessionCookie, {\n maxAge: expiresIn,\n httpOnly: true,\n secure: process.env.NODE_ENV === 'production',\n path: '/',\n });\n return { success: true, message: 'Session created' };\n } catch (error) {\n return { success: false, message: 'Failed to create session' };\n }\n}\n\n\n\nexport async function getServerSessionCookie() {\n const cookieStore = await cookies();\n const sessionCookie = cookieStore.get('_session_cookie')?.value;\n\n if (!sessionCookie) {\n throw new Error('No session cookie found')\n }\n \n try {\n const decondeClaims = await adminAuth.verifySessionCookie(sessionCookie, true)\n return {\n token: sessionCookie,\n userId: decondeClaims.uid\n }\n } catch (error) {\n console.error('Error verifying session:', error)\n throw new Error('Invalid Session')\n }\n}\n\n\nexport async function getIdToken() {\n const cookieStore = await cookies();\n const token = cookieStore.get('_session_token')?.value;\n\n if (!token) {\n throw new Error('No session cookie found')\n }\n \n try {\n const decodedClaims = await adminAuth.verifyIdToken(token)\n return {\n token: token,\n userId: decodedClaims.uid\n }\n } catch (error) {\n console.error('Error verifying session:', error)\n throw new Error('Invalid Session')\n }\n}\n\nexport async function setServerSession(token: string) {\n const cookieStore = await cookies();\n cookieStore.set('_session', token, {\n httpOnly: true,\n secure: process.env.NODE_ENV === 'production',\n sameSite: 'strict',\n maxAge: 60 * 60, // 1 hour\n path: '/',\n });\n }\n\n export async function verifyTernIdToken(token: string): Promise<{ valid: boolean; uid?: string; error?: string }> {\n try {\n const decodedToken = await adminAuth.verifyIdToken(token, true);\n return { valid: true, uid: decodedToken.uid };\n } catch (error) {\n if (error instanceof Error) {\n const firebaseError = error as FirebaseAuthError;\n if (error.name === 'FirebaseAuthError') {\n // Handle specific Firebase Auth errors\n switch (firebaseError.code) {\n case 'auth/id-token-expired':\n return { valid: false, error: 'Token has expired' };\n case 'auth/id-token-revoked':\n return { valid: false, error: 'Token has been revoked' };\n case 'auth/user-disabled':\n return { valid: false, error: 'User account has been disabled' };\n default:\n return { valid: false, error: 'Invalid token' };\n }\n }\n }\n return { valid: false, error: 'Error verifying token' };\n }\n }\n \n\n export async function verifyTernSessionCookie(session: string): Promise<{ valid: boolean; uid?: any; error?: any }>{\n try {\n const res = await adminAuth.verifySessionCookie(session, true);\n if (res) {\n return { valid: true, uid: res.uid };\n } else {\n return { valid: false, error: 'Invalid session'};\n }\n } catch (error) {\n return {error: error, valid: false}\n }\n }\n\n\n export async function clearSessionCookie() {\n const cookieStore = await cookies()\n \n cookieStore.delete('_session_cookie')\n cookieStore.delete('_session_token')\n cookieStore.delete('_session')\n \n try {\n // Verify if there's an active session before revoking\n const sessionCookie = cookieStore.get('_session_cookie')?.value\n if (sessionCookie) {\n // Get the decoded claims to get the user's ID\n const decodedClaims = await adminAuth.verifySessionCookie(sessionCookie)\n \n // Revoke all sessions for the user\n await adminAuth.revokeRefreshTokens(decodedClaims.uid)\n }\n \n return { success: true, message: 'Session cleared successfully' }\n } catch (error) {\n console.error('Error clearing session:', error)\n // Still return success even if revoking fails, as cookies are cleared\n return { success: true, message: 'Session cookies cleared' }\n }\n }\n\n\n\n/*\n export async function GET(request: NextRequest) {\n const cookieStore = await cookies();\n const sessionCookie = cookieStore.get('session')?.value\n \n if (!sessionCookie) {\n return NextResponse.json({ isAuthenticated: false }, { status: 401 })\n }\n \n try {\n const decodedClaims = await adminAuth.verifySessionCookie(sessionCookie, true)\n return NextResponse.json({ isAuthenticated: true, user: decodedClaims }, { status: 200 })\n } catch (error) {\n console.error('Error verifying session cookie:', error)\n return NextResponse.json({ isAuthenticated: false }, { status: 401 })\n }\n }\n\n*/"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../../src/app-router/server/sessionTernSecure.ts"],"sourcesContent":["'use server'\n\nimport { cookies } from 'next/headers';\nimport { adminTernSecureAuth as adminAuth } from '../../utils/admin-init';\n\ninterface FirebaseAuthError extends Error {\n code?: string;\n}\n\nexport interface User {\n uid: string | null;\n email: string | null;\n }\n\nexport interface Session {\n user: User | null;\n token: string | null;\n error: Error | null;\n}\n\nexport async function createSessionCookie(idToken: string) {\n try {\n const expiresIn = 60 * 60 * 24 * 5 * 1000;\n const sessionCookie = await adminAuth.createSessionCookie(idToken, { expiresIn });\n\n const cookieStore = await cookies();\n cookieStore.set('_session_cookie', sessionCookie, {\n maxAge: expiresIn,\n httpOnly: true,\n secure: process.env.NODE_ENV === 'production',\n path: '/',\n });\n return { success: true, message: 'Session created' };\n } catch (error) {\n return { success: false, message: 'Failed to create session' };\n }\n}\n\n\n\nexport async function getServerSessionCookie() {\n const cookieStore = await cookies();\n const sessionCookie = cookieStore.get('_session_cookie')?.value;\n\n if (!sessionCookie) {\n throw new Error('No session cookie found')\n }\n \n try {\n const decondeClaims = await adminAuth.verifySessionCookie(sessionCookie, true)\n return {\n token: sessionCookie,\n userId: decondeClaims.uid\n }\n } catch (error) {\n console.error('Error verifying session:', error)\n throw new Error('Invalid Session')\n }\n}\n\n\nexport async function getIdToken() {\n const cookieStore = await cookies();\n const token = cookieStore.get('_session_token')?.value;\n\n if (!token) {\n throw new Error('No session cookie found')\n }\n \n try {\n const decodedClaims = await adminAuth.verifyIdToken(token)\n return {\n token: token,\n userId: decodedClaims.uid\n }\n } catch (error) {\n console.error('Error verifying session:', error)\n throw new Error('Invalid Session')\n }\n}\n\nexport async function setServerSession(token: string) {\n try {\n const cookieStore = await cookies();\n cookieStore.set('_session_token', token, {\n httpOnly: true,\n secure: process.env.NODE_ENV === 'production',\n sameSite: 'strict',\n maxAge: 60 * 60, // 1 hour\n path: '/',\n });\n return { success: true, message: 'Session created' };\n } catch {\n return { success: false, message: 'Failed to create session' };\n }\n}\n\n export async function verifyTernIdToken(token: string): Promise<{ valid: boolean; uid?: string; error?: string }> {\n try {\n const decodedToken = await adminAuth.verifyIdToken(token, true);\n return { valid: true, uid: decodedToken.uid };\n } catch (error) {\n if (error instanceof Error) {\n const firebaseError = error as FirebaseAuthError;\n if (error.name === 'FirebaseAuthError') {\n // Handle specific Firebase Auth errors\n switch (firebaseError.code) {\n case 'auth/id-token-expired':\n return { valid: false, error: 'Token has expired' };\n case 'auth/id-token-revoked':\n return { valid: false, error: 'Token has been revoked' };\n case 'auth/user-disabled':\n return { valid: false, error: 'User account has been disabled' };\n default:\n return { valid: false, error: 'Invalid token' };\n }\n }\n }\n return { valid: false, error: 'Error verifying token' };\n }\n }\n \n\n export async function verifyTernSessionCookie(session: string): Promise<{ valid: boolean; uid?: any; error?: any }>{\n try {\n const res = await adminAuth.verifySessionCookie(session, true);\n if (res) {\n return { valid: true, uid: res.uid };\n } else {\n return { valid: false, error: 'Invalid session'};\n }\n } catch (error) {\n return {error: error, valid: false}\n }\n }\n\n\n export async function clearSessionCookie() {\n const cookieStore = await cookies()\n \n cookieStore.delete('_session_cookie')\n cookieStore.delete('_session_token')\n cookieStore.delete('_session')\n \n try {\n // Verify if there's an active session before revoking\n const sessionCookie = cookieStore.get('_session_cookie')?.value\n if (sessionCookie) {\n // Get the decoded claims to get the user's ID\n const decodedClaims = await adminAuth.verifySessionCookie(sessionCookie)\n \n // Revoke all sessions for the user\n await adminAuth.revokeRefreshTokens(decodedClaims.uid)\n }\n \n return { success: true, message: 'Session cleared successfully' }\n } catch (error) {\n console.error('Error clearing session:', error)\n // Still return success even if revoking fails, as cookies are cleared\n return { success: true, message: 'Session cookies cleared' }\n }\n }\n\n\n\n/*\n export async function GET(request: NextRequest) {\n const cookieStore = await cookies();\n const sessionCookie = cookieStore.get('session')?.value\n \n if (!sessionCookie) {\n return NextResponse.json({ isAuthenticated: false }, { status: 401 })\n }\n \n try {\n const decodedClaims = await adminAuth.verifySessionCookie(sessionCookie, true)\n return NextResponse.json({ isAuthenticated: true, user: decodedClaims }, { status: 200 })\n } catch (error) {\n console.error('Error verifying session cookie:', error)\n return NextResponse.json({ isAuthenticated: false }, { status: 401 })\n }\n }\n\n*/"],"mappings":"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","names":[]}
@@ -1,26 +1,145 @@
1
1
  import { NextResponse } from "next/server";
2
- import { auth } from "./auth";
3
- function ternSecureMiddleware(options = {}) {
4
- const { publicPaths = [], redirectTo = "/login" } = options;
5
- return async function middleware(request) {
2
+ import { cookies } from "next/headers";
3
+ import { verifySession } from "./edge-session";
4
+ import { verifyFirebaseToken } from "./jwt";
5
+ const runtime = "edge";
6
+ function createRouteMatcher(patterns) {
7
+ return (request) => {
8
+ const { pathname } = request.nextUrl;
9
+ return patterns.some((pattern) => {
10
+ const regexPattern = new RegExp(
11
+ `^${pattern.replace(/\*/g, ".*").replace(/\((.*)\)/, "(?:$1)?")}$`
12
+ );
13
+ return regexPattern.test(pathname);
14
+ });
15
+ };
16
+ }
17
+ async function edgeAuth(request) {
18
+ var _a, _b, _c, _d, _e, _f;
19
+ const cookieStore = await cookies();
20
+ async function protect() {
6
21
  const { pathname } = request.nextUrl;
7
- if (publicPaths.includes(pathname)) {
8
- return NextResponse.next();
22
+ throw new Error("Unauthorized access");
23
+ }
24
+ try {
25
+ const sessionCookie = (_a = cookieStore.get("_session_cookie")) == null ? void 0 : _a.value;
26
+ if (sessionCookie) {
27
+ const userInfo = await verifyFirebaseToken(sessionCookie, true);
28
+ console.log("userInfo", userInfo);
29
+ if (userInfo.valid) {
30
+ return {
31
+ user: {
32
+ uid: (_b = userInfo.uid) != null ? _b : "",
33
+ email: (_c = userInfo.email) != null ? _c : null
34
+ },
35
+ token: sessionCookie,
36
+ protect: async () => {
37
+ }
38
+ };
39
+ }
9
40
  }
41
+ const idToken = (_d = cookieStore.get("_session_token")) == null ? void 0 : _d.value;
42
+ if (idToken) {
43
+ const userInfo = await verifyFirebaseToken(idToken, false);
44
+ if (userInfo.valid) {
45
+ return {
46
+ user: {
47
+ uid: (_e = userInfo.uid) != null ? _e : "",
48
+ email: (_f = userInfo.email) != null ? _f : null
49
+ },
50
+ token: idToken,
51
+ protect: async () => {
52
+ }
53
+ };
54
+ }
55
+ }
56
+ return {
57
+ user: null,
58
+ token: null,
59
+ protect
60
+ };
61
+ } catch (error) {
62
+ return {
63
+ user: null,
64
+ token: null,
65
+ protect
66
+ };
67
+ }
68
+ }
69
+ async function edgeAuthSecond(request) {
70
+ var _a, _b;
71
+ async function protect() {
72
+ throw new Error("Unauthorized access");
73
+ }
74
+ try {
75
+ const sessionResult = await verifySession(request);
76
+ if (sessionResult.isAuthenticated && sessionResult.user) {
77
+ return {
78
+ user: sessionResult.user,
79
+ token: ((_a = request.cookies.get("_session_cookie")) == null ? void 0 : _a.value) || ((_b = request.cookies.get("_session_token")) == null ? void 0 : _b.value) || null,
80
+ protect: async () => {
81
+ }
82
+ };
83
+ }
84
+ return {
85
+ user: null,
86
+ token: null,
87
+ protect
88
+ };
89
+ } catch (error) {
90
+ console.error("Auth check error:", error);
91
+ return {
92
+ user: null,
93
+ token: null,
94
+ protect
95
+ };
96
+ }
97
+ }
98
+ function ternSecureMiddleware(callback) {
99
+ return async function middleware(request) {
10
100
  try {
11
- const { userId, token, error } = await auth();
12
- if (error || !userId || !token) {
13
- return NextResponse.redirect(new URL(redirectTo, request.url));
101
+ const auth = await edgeAuthSecond(request);
102
+ try {
103
+ await callback(auth, request);
104
+ const response = NextResponse.next();
105
+ if (auth.user) {
106
+ response.headers.set("x-user-id", auth.user.uid);
107
+ if (auth.user.email) {
108
+ response.headers.set("x-user-email", auth.user.email);
109
+ }
110
+ if (auth.user.emailVerified !== void 0) {
111
+ response.headers.set("x-email-verified", auth.user.emailVerified.toString());
112
+ }
113
+ if (auth.user.authTime) {
114
+ response.headers.set("x-auth-time", auth.user.authTime.toString());
115
+ }
116
+ }
117
+ return response;
118
+ } catch (error) {
119
+ if (error instanceof Error && error.message === "Unauthorized access") {
120
+ const redirectUrl = new URL("/sign-in", request.url);
121
+ redirectUrl.searchParams.set("redirect", request.nextUrl.pathname);
122
+ return NextResponse.redirect(redirectUrl);
123
+ }
124
+ throw error;
14
125
  }
15
- const response = NextResponse.next();
16
- return response;
17
126
  } catch (error) {
18
- console.error("Error in ternSecureMiddleware:", error);
19
- return NextResponse.redirect(new URL(redirectTo, request.url));
127
+ console.error("Middleware error:", {
128
+ error: error instanceof Error ? {
129
+ name: error.name,
130
+ message: error.message,
131
+ stack: error.stack
132
+ } : error,
133
+ path: request.nextUrl.pathname
134
+ });
135
+ const redirectUrl = new URL("/sign-in", request.url);
136
+ return NextResponse.redirect(redirectUrl);
20
137
  }
21
138
  };
22
139
  }
23
140
  export {
141
+ createRouteMatcher,
142
+ runtime,
24
143
  ternSecureMiddleware
25
144
  };
26
145
  //# sourceMappingURL=ternSecureMiddleware.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/app-router/server/ternSecureMiddleware.ts"],"sourcesContent":["import { NextRequest, NextResponse } from 'next/server';\nimport { auth } from './auth';\n\nexport interface TernSecureMiddlewareOptions {\n publicPaths?: string[];\n redirectTo?: string;\n}\n\nexport function ternSecureMiddleware(options: TernSecureMiddlewareOptions = {}) {\n const { publicPaths = [], redirectTo = '/login' } = options;\n\n return async function middleware(request: NextRequest) {\n const { pathname } = request.nextUrl;\n\n // Check if the path is public\n if (publicPaths.includes(pathname)) {\n return NextResponse.next();\n }\n\n try {\n const { userId, token, error } = await auth();\n\n if (error || !userId || !token) {\n // If there's no valid session, redirect to login\n return NextResponse.redirect(new URL(redirectTo, request.url));\n }\n\n // If there's a valid session, allow the request to proceed\n const response = NextResponse.next();\n \n // Optionally, you can set headers here if needed\n // response.headers.set('X-User-ID', userId);\n\n return response;\n } catch (error) {\n console.error('Error in ternSecureMiddleware:', error);\n return NextResponse.redirect(new URL(redirectTo, request.url));\n }\n };\n}\n\n"],"mappings":"AAAA,SAAsB,oBAAoB;AAC1C,SAAS,YAAY;AAOd,SAAS,qBAAqB,UAAuC,CAAC,GAAG;AAC9E,QAAM,EAAE,cAAc,CAAC,GAAG,aAAa,SAAS,IAAI;AAEpD,SAAO,eAAe,WAAW,SAAsB;AACrD,UAAM,EAAE,SAAS,IAAI,QAAQ;AAG7B,QAAI,YAAY,SAAS,QAAQ,GAAG;AAClC,aAAO,aAAa,KAAK;AAAA,IAC3B;AAEA,QAAI;AACF,YAAM,EAAE,QAAQ,OAAO,MAAM,IAAI,MAAM,KAAK;AAE5C,UAAI,SAAS,CAAC,UAAU,CAAC,OAAO;AAE9B,eAAO,aAAa,SAAS,IAAI,IAAI,YAAY,QAAQ,GAAG,CAAC;AAAA,MAC/D;AAGA,YAAM,WAAW,aAAa,KAAK;AAKnC,aAAO;AAAA,IACT,SAAS,OAAO;AACd,cAAQ,MAAM,kCAAkC,KAAK;AACrD,aAAO,aAAa,SAAS,IAAI,IAAI,YAAY,QAAQ,GAAG,CAAC;AAAA,IAC/D;AAAA,EACF;AACF;","names":[]}
1
+ {"version":3,"sources":["../../../../src/app-router/server/ternSecureMiddleware.ts"],"sourcesContent":["import { NextRequest, NextResponse } from 'next/server';\nimport { cookies } from 'next/headers'\nimport { verifySession, type UserInfo } from './edge-session'\nimport { verifyFirebaseToken } from './jwt';\n\nexport const runtime = \"edge\"\n\n\ninterface Auth {\n user: UserInfo | null\n token: string | null\n protect: () => Promise<void>\n}\n\ntype MiddlewareCallback = (\n auth: Auth,\n request: NextRequest\n) => Promise<void>\n\n\n/**\n * Create a route matcher function for public paths\n */\nexport function createRouteMatcher(patterns: string[]) {\n return (request: NextRequest): boolean => {\n const { pathname } = request.nextUrl\n return patterns.some(pattern => {\n // Convert route pattern to regex\n const regexPattern = new RegExp(\n `^${pattern.replace(/\\*/g, '.*').replace(/\\((.*)\\)/, '(?:$1)?')}$`\n )\n return regexPattern.test(pathname)\n })\n }\n}\n\n\n/**\n * Edge-compatible auth check\n */\nasync function edgeAuth(request: NextRequest): Promise<Auth>{\n const cookieStore = await cookies()\n\n async function protect() {\n const { pathname } = request.nextUrl\n throw new Error('Unauthorized access')\n }\n\n try {\n // First try session cookie\n const sessionCookie = cookieStore.get(\"_session_cookie\")?.value\n if (sessionCookie) {\n const userInfo = await verifyFirebaseToken(sessionCookie, true)\n console.log('userInfo', userInfo)\n if (userInfo.valid) {\n return { \n user: {\n uid: userInfo.uid ?? '',\n email: userInfo.email ?? null,\n },\n token: sessionCookie,\n protect: async () => {}\n }\n }\n }\n\n // Then try ID token\n const idToken = cookieStore.get(\"_session_token\")?.value\n if (idToken) {\n const userInfo = await verifyFirebaseToken(idToken, false)\n if (userInfo.valid) {\n return { \n user: {\n uid: userInfo.uid ?? '',\n email: userInfo.email ?? null,\n },\n token: idToken,\n protect: async () => {}\n }\n }\n }\n\n return {\n user: null,\n token: null,\n protect\n }\n } catch (error) {\n return {\n user: null,\n token: null,\n protect\n }\n }\n}\n\n/**\n * Edge-compatible auth check\n */\nasync function edgeAuthSecond(request: NextRequest): Promise<Auth> {\n async function protect() {\n throw new Error(\"Unauthorized access\")\n }\n\n try {\n const sessionResult = await verifySession(request)\n\n if (sessionResult.isAuthenticated && sessionResult.user) {\n return {\n user: sessionResult.user,\n token: request.cookies.get(\"_session_cookie\")?.value || request.cookies.get(\"_session_token\")?.value || null,\n protect: async () => {},\n }\n }\n\n return {\n user: null,\n token: null,\n protect,\n }\n } catch (error) {\n console.error(\"Auth check error:\", error)\n return {\n user: null,\n token: null,\n protect,\n }\n }\n}\n\n\n\n/**\n * Middleware factory that handles authentication and custom logic\n * @param customHandler Optional function for additional custom logic\n */\n\nexport function ternSecureMiddleware(callback: MiddlewareCallback) {\n return async function middleware(request: NextRequest) {\n try {\n const auth = await edgeAuthSecond(request)\n\n try {\n \n await callback(auth, request)\n\n const response = NextResponse.next()\n\n if (auth.user) {\n // Set auth headers\n response.headers.set(\"x-user-id\", auth.user.uid)\n if (auth.user.email) {\n response.headers.set(\"x-user-email\", auth.user.email)\n }\n if (auth.user.emailVerified !== undefined) {\n response.headers.set(\"x-email-verified\", auth.user.emailVerified.toString())\n }\n if (auth.user.authTime) {\n response.headers.set(\"x-auth-time\", auth.user.authTime.toString())\n }\n }\n\n return response\n } catch (error) {\n // Handle unauthorized access\n if (error instanceof Error && error.message === 'Unauthorized access') {\n const redirectUrl = new URL('/sign-in', request.url)\n redirectUrl.searchParams.set('redirect', request.nextUrl.pathname)\n return NextResponse.redirect(redirectUrl)\n }\n throw error\n }\n\n } catch (error) {\n console.error(\"Middleware error:\", {\n error:\n error instanceof Error\n ? {\n name: error.name,\n message: error.message,\n stack: error.stack,\n }\n : error,\n path: request.nextUrl.pathname,\n })\n\n const redirectUrl = new URL(\"/sign-in\", request.url)\n return NextResponse.redirect(redirectUrl)\n }\n }\n}\n"],"mappings":"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","names":[]}
@@ -4,14 +4,20 @@ import { useState, useEffect, useMemo, useCallback } from "react";
4
4
  import { ternSecureAuth } from "../utils/client-init";
5
5
  import { onAuthStateChanged } from "firebase/auth";
6
6
  import { TernSecureCtx } from "./TernSecureCtx";
7
- import { useRouter } from "next/navigation";
7
+ import { useRouter, usePathname } from "next/navigation";
8
+ import { isBaseAuthRoute, isInternalRoute, isAuthRoute } from "../app-router/route-handler/internal-route";
9
+ import { hasRedirectLoop } from "../utils/construct";
8
10
  function TernSecureClientProvider({
9
11
  children,
10
- loginPath = "/sign-in",
11
- loadingComponent
12
+ loginPath = process.env.NEXT_PUBLIC_SIGN_IN_PATH || "/sign-in",
13
+ signUpPath = process.env.NEXT_PUBLIC_SIGN_UP_PATH || "/sign-up",
14
+ loadingComponent,
15
+ requiresVerification
12
16
  }) {
13
17
  const auth = useMemo(() => ternSecureAuth, []);
14
18
  const router = useRouter();
19
+ const pathname = usePathname();
20
+ const [isRedirecting, setIsRedirecting] = useState(false);
15
21
  const [authState, setAuthState] = useState(() => ({
16
22
  userId: null,
17
23
  isLoaded: false,
@@ -20,9 +26,58 @@ function TernSecureClientProvider({
20
26
  isVerified: false,
21
27
  isAuthenticated: false,
22
28
  token: null,
23
- email: null
29
+ email: null,
30
+ status: "loading",
31
+ requiresVerification
24
32
  }));
33
+ const constructUrlWithRedirect = useCallback(
34
+ (loginPath2, currentPath, loginPathParam, signUpPathParam) => {
35
+ const baseUrl = window.location.origin;
36
+ const signInUrl = new URL(loginPath2, baseUrl);
37
+ if (!currentPath.includes(loginPathParam) && !currentPath.includes(signUpPathParam)) {
38
+ signInUrl.searchParams.set("redirect", currentPath);
39
+ }
40
+ return signInUrl.toString();
41
+ },
42
+ []
43
+ );
44
+ const shouldRedirect = useCallback(
45
+ (pathname2, isVerified) => {
46
+ const searchParams = new URLSearchParams(window.location.search);
47
+ if (isBaseAuthRoute(pathname2) && !searchParams.has("redirect")) {
48
+ return false;
49
+ }
50
+ if (isInternalRoute(pathname2)) {
51
+ return false;
52
+ }
53
+ if (isAuthRoute(pathname2) && (!requiresVerification || isVerified)) {
54
+ return false;
55
+ }
56
+ return true;
57
+ },
58
+ [requiresVerification]
59
+ );
60
+ const redirectToLogin = useCallback(
61
+ (currentPath) => {
62
+ const path = currentPath || pathname || "/";
63
+ if (isInternalRoute(path)) {
64
+ return;
65
+ }
66
+ if (hasRedirectLoop(path, loginPath)) {
67
+ return;
68
+ }
69
+ setIsRedirecting(true);
70
+ const loginUrl = constructUrlWithRedirect(loginPath, path, loginPath, signUpPath);
71
+ if (process.env.NODE_ENV === "production") {
72
+ window.location.href = loginUrl;
73
+ } else {
74
+ router.push(loginUrl);
75
+ }
76
+ },
77
+ [router, loginPath, signUpPath, pathname, constructUrlWithRedirect]
78
+ );
25
79
  const handleSignOut = useCallback(async (error) => {
80
+ const currentPath = window.location.pathname;
26
81
  await auth.signOut();
27
82
  setAuthState({
28
83
  isLoaded: true,
@@ -32,56 +87,124 @@ function TernSecureClientProvider({
32
87
  token: null,
33
88
  email: null,
34
89
  isVerified: false,
35
- isAuthenticated: false
90
+ isAuthenticated: false,
91
+ status: "unauthenticated",
92
+ requiresVerification
36
93
  });
37
- router.push(loginPath);
38
- }, [auth, router, loginPath]);
94
+ redirectToLogin(currentPath);
95
+ }, [auth, redirectToLogin, requiresVerification]);
39
96
  const setEmail = useCallback((email) => {
40
97
  setAuthState((prev) => ({
41
98
  ...prev,
42
99
  email
43
100
  }));
44
101
  }, []);
102
+ const getAuthError = useCallback(() => {
103
+ if (authState.error) {
104
+ const error = authState.error;
105
+ return {
106
+ success: false,
107
+ message: error.message,
108
+ error: error.code,
109
+ user: null
110
+ };
111
+ }
112
+ if (authState.requiresVerification && authState.isValid && !authState.isVerified) {
113
+ return {
114
+ success: false,
115
+ message: "Email verification required",
116
+ error: "EMAIL_NOT_VERIFIED",
117
+ user: null
118
+ };
119
+ }
120
+ if (!authState.isAuthenticated && authState.status !== "loading") {
121
+ return {
122
+ success: false,
123
+ message: "User is not authenticated",
124
+ error: "AUTHENTICATED",
125
+ user: null
126
+ };
127
+ }
128
+ return {
129
+ success: true,
130
+ user: ternSecureAuth.currentUser
131
+ };
132
+ }, [
133
+ authState.error,
134
+ authState.isValid,
135
+ authState.isVerified,
136
+ authState.isAuthenticated,
137
+ authState.status,
138
+ authState.requiresVerification
139
+ ]);
45
140
  useEffect(() => {
46
- const unsubscribe = onAuthStateChanged(auth, async (user) => {
47
- if (user) {
48
- const isValid = !!user.uid;
49
- const isVerified = user.emailVerified;
50
- setAuthState({
51
- isLoaded: true,
52
- userId: user.uid,
53
- isValid,
54
- isVerified,
55
- isAuthenticated: isValid && isVerified,
56
- token: user.getIdToken(),
57
- error: null,
58
- email: user.email
59
- });
60
- } else {
61
- setAuthState({
62
- isLoaded: true,
63
- userId: null,
64
- isValid: false,
65
- isVerified: false,
66
- isAuthenticated: false,
67
- token: null,
68
- error: new Error("User is not authenticated"),
69
- email: null
70
- });
71
- if (!window.location.pathname.includes("/sign-up")) {
72
- router.push(loginPath);
141
+ let mounted = true;
142
+ let initialLoad = true;
143
+ const unsubscribe = onAuthStateChanged(
144
+ auth,
145
+ async (user) => {
146
+ if (!mounted) return;
147
+ try {
148
+ if (user) {
149
+ const isValid = !!user.uid;
150
+ const isVerified = user.emailVerified;
151
+ const isAuthenticated = isValid && (!requiresVerification || isVerified);
152
+ setAuthState({
153
+ isLoaded: true,
154
+ userId: user.uid,
155
+ isValid,
156
+ isVerified,
157
+ isAuthenticated: isValid && isVerified,
158
+ token: user.getIdToken(),
159
+ error: null,
160
+ email: user.email,
161
+ status: isAuthenticated ? "authenticated" : "unverified",
162
+ requiresVerification
163
+ });
164
+ if (requiresVerification && !isVerified && shouldRedirect(pathname || "", isVerified)) {
165
+ if (initialLoad || !isRedirecting) {
166
+ redirectToLogin(pathname);
167
+ }
168
+ }
169
+ } else {
170
+ setAuthState({
171
+ isLoaded: true,
172
+ userId: null,
173
+ isValid: false,
174
+ isVerified: false,
175
+ isAuthenticated: false,
176
+ token: null,
177
+ error: null,
178
+ email: null,
179
+ status: "unauthenticated",
180
+ requiresVerification
181
+ });
182
+ if (shouldRedirect(pathname || "", false) && initialLoad) {
183
+ redirectToLogin();
184
+ }
185
+ }
186
+ } catch (error) {
187
+ console.error("Auth state change error:", error);
188
+ if (mounted) {
189
+ handleSignOut(error instanceof Error ? error : new Error("Authentication error occurred"));
190
+ }
191
+ } finally {
192
+ initialLoad = false;
73
193
  }
74
194
  }
75
- }, (error) => {
76
- handleSignOut(error instanceof Error ? error : new Error("Authentication error occurred"));
77
- });
78
- return () => unsubscribe();
79
- }, [auth, handleSignOut, router, loginPath]);
195
+ );
196
+ return () => {
197
+ mounted = false;
198
+ unsubscribe();
199
+ };
200
+ }, [auth, handleSignOut, redirectToLogin, requiresVerification, pathname, isRedirecting, shouldRedirect]);
80
201
  const contextValue = useMemo(() => ({
81
202
  ...authState,
82
203
  signOut: handleSignOut,
83
- setEmail
84
- }), [authState, auth, handleSignOut, setEmail]);
204
+ setEmail,
205
+ getAuthError,
206
+ redirectToLogin
207
+ }), [authState, handleSignOut, setEmail, getAuthError, redirectToLogin]);
85
208
  if (!authState.isLoaded) {
86
209
  return /* @__PURE__ */ jsx(TernSecureCtx.Provider, { value: contextValue, children: loadingComponent || /* @__PURE__ */ jsx("div", { "aria-live": "polite", "aria-busy": "true", children: /* @__PURE__ */ jsx("span", { className: "sr-only", children: "Loading authentication state..." }) }) });
87
210
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/boundary/TernSecureClientProvider.tsx"],"sourcesContent":["\"use client\"\n\nimport React, { useState, useEffect, useMemo, useCallback } from 'react'\nimport { ternSecureAuth } from '../utils/client-init'\nimport { onAuthStateChanged, User } from \"firebase/auth\"\nimport { TernSecureCtx, TernSecureCtxValue } from './TernSecureCtx'\nimport { type TernSecureState } from '../types'\nimport { useRouter } from 'next/navigation'\n\ninterface TernSecureClientProviderProps {\n children: React.ReactNode;\n onUserChanged?: (user: User | null) => Promise<void>;\n loginPath?: string;\n loadingComponent?: React.ReactNode;\n}\n\nexport function TernSecureClientProvider({ \n children, \n loginPath = '/sign-in',\n loadingComponent\n}: TernSecureClientProviderProps) {\n const auth = useMemo(() => ternSecureAuth, []);\n const router = useRouter();\n const [authState, setAuthState] = useState<TernSecureState>(() => ({\n userId: null,\n isLoaded: false,\n error: null,\n isValid: false,\n isVerified: false,\n isAuthenticated: false,\n token: null,\n email: null,\n }));\n\n const handleSignOut = useCallback(async (error?: Error) => {\n await auth.signOut();\n setAuthState({\n isLoaded: true,\n userId: null,\n error: error || null,\n isValid: false,\n token: null,\n email: null,\n isVerified: false,\n isAuthenticated: false,\n });\n router.push(loginPath);\n }, [auth, router, loginPath]);\n\n const setEmail = useCallback((email: string) => {\n setAuthState((prev) => ({\n ...prev,\n email,\n }))\n }, [])\n\nuseEffect(() => {\n const unsubscribe = onAuthStateChanged(auth, async (user: User | null) => {\n if (user) {\n const isValid = !!user.uid;\n const isVerified = user.emailVerified;\n setAuthState({\n isLoaded: true,\n userId: user.uid,\n isValid,\n isVerified,\n isAuthenticated: isValid && isVerified,\n token: user.getIdToken(),\n error: null,\n email: user.email,\n })\n } else {\n setAuthState({\n isLoaded: true,\n userId: null,\n isValid: false,\n isVerified: false,\n isAuthenticated: false,\n token: null,\n error: new Error('User is not authenticated'),\n email: null,\n })\n if (!window.location.pathname.includes(\"/sign-up\")) {\n router.push(loginPath)\n }\n }\n }, (error) => {\n handleSignOut(error instanceof Error ? error : new Error('Authentication error occurred'));\n })\n \n return () => unsubscribe()\n }, [auth, handleSignOut, router, loginPath])\n\n const contextValue: TernSecureCtxValue = useMemo(() => ({\n ...authState,\n signOut: handleSignOut,\n setEmail\n }), [authState, auth, handleSignOut, setEmail]);\n\n if (!authState.isLoaded) {\n return (\n <TernSecureCtx.Provider value={contextValue}>\n {loadingComponent || (\n <div aria-live=\"polite\" aria-busy=\"true\">\n <span className=\"sr-only\">Loading authentication state...</span>\n </div>\n )}\n </TernSecureCtx.Provider>\n );\n }\n\n return (\n <TernSecureCtx.Provider value={contextValue}>\n {children}\n </TernSecureCtx.Provider>\n )\n}"],"mappings":";AAwGY;AAtGZ,SAAgB,UAAU,WAAW,SAAS,mBAAmB;AACjE,SAAS,sBAAsB;AAC/B,SAAS,0BAAgC;AACzC,SAAS,qBAAyC;AAElD,SAAS,iBAAiB;AASnB,SAAS,yBAAyB;AAAA,EACvC;AAAA,EACA,YAAY;AAAA,EACZ;AACF,GAAkC;AAChC,QAAM,OAAO,QAAQ,MAAM,gBAAgB,CAAC,CAAC;AAC7C,QAAM,SAAS,UAAU;AACzB,QAAM,CAAC,WAAW,YAAY,IAAI,SAA0B,OAAO;AAAA,IACjE,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,OAAO;AAAA,IACP,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,iBAAiB;AAAA,IACjB,OAAO;AAAA,IACP,OAAO;AAAA,EACT,EAAE;AAEF,QAAM,gBAAgB,YAAY,OAAO,UAAkB;AACzD,UAAM,KAAK,QAAQ;AACnB,iBAAa;AAAA,MACX,UAAU;AAAA,MACV,QAAQ;AAAA,MACR,OAAO,SAAS;AAAA,MAChB,SAAS;AAAA,MACT,OAAO;AAAA,MACP,OAAO;AAAA,MACP,YAAY;AAAA,MACZ,iBAAiB;AAAA,IACnB,CAAC;AACD,WAAO,KAAK,SAAS;AAAA,EACvB,GAAG,CAAC,MAAM,QAAQ,SAAS,CAAC;AAE5B,QAAM,WAAW,YAAY,CAAC,UAAkB;AAC9C,iBAAa,CAAC,UAAU;AAAA,MACtB,GAAG;AAAA,MACH;AAAA,IACF,EAAE;AAAA,EACJ,GAAG,CAAC,CAAC;AAEP,YAAU,MAAM;AACZ,UAAM,cAAc,mBAAmB,MAAM,OAAO,SAAsB;AACxE,UAAI,MAAM;AACR,cAAM,UAAU,CAAC,CAAC,KAAK;AACvB,cAAM,aAAa,KAAK;AACxB,qBAAa;AAAA,UACX,UAAU;AAAA,UACV,QAAQ,KAAK;AAAA,UACb;AAAA,UACA;AAAA,UACA,iBAAiB,WAAW;AAAA,UAC5B,OAAO,KAAK,WAAW;AAAA,UACvB,OAAO;AAAA,UACP,OAAO,KAAK;AAAA,QACd,CAAC;AAAA,MACH,OAAO;AACL,qBAAa;AAAA,UACX,UAAU;AAAA,UACV,QAAQ;AAAA,UACR,SAAS;AAAA,UACT,YAAY;AAAA,UACZ,iBAAiB;AAAA,UACjB,OAAO;AAAA,UACP,OAAO,IAAI,MAAM,2BAA2B;AAAA,UAC5C,OAAO;AAAA,QACT,CAAC;AACD,YAAI,CAAC,OAAO,SAAS,SAAS,SAAS,UAAU,GAAG;AAClD,iBAAO,KAAK,SAAS;AAAA,QACvB;AAAA,MACF;AAAA,IACF,GAAG,CAAC,UAAU;AACZ,oBAAc,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,+BAA+B,CAAC;AAAA,IAC3F,CAAC;AAED,WAAO,MAAM,YAAY;AAAA,EAC3B,GAAG,CAAC,MAAM,eAAe,QAAQ,SAAS,CAAC;AAE3C,QAAM,eAAmC,QAAQ,OAAO;AAAA,IACtD,GAAG;AAAA,IACH,SAAS;AAAA,IACT;AAAA,EACF,IAAI,CAAC,WAAW,MAAM,eAAe,QAAQ,CAAC;AAE9C,MAAI,CAAC,UAAU,UAAU;AACvB,WACE,oBAAC,cAAc,UAAd,EAAuB,OAAO,cAC5B,8BACC,oBAAC,SAAI,aAAU,UAAS,aAAU,QAChC,8BAAC,UAAK,WAAU,WAAU,6CAA+B,GAC3D,GAEJ;AAAA,EAEJ;AAEA,SACI,oBAAC,cAAc,UAAd,EAAuB,OAAO,cAC7B,UACF;AAEN;","names":[]}
1
+ {"version":3,"sources":["../../../src/boundary/TernSecureClientProvider.tsx"],"sourcesContent":["\"use client\"\n\nimport React, { useState, useEffect, useMemo, useCallback } from 'react'\nimport { ternSecureAuth } from '../utils/client-init'\nimport { onAuthStateChanged, User } from \"firebase/auth\"\nimport { TernSecureCtx, TernSecureCtxValue } from './TernSecureCtx'\nimport type { TernSecureState, AuthError, SignInResponse } from \"../types\"\nimport type { ERRORS } from '../errors'\nimport { useRouter, usePathname } from 'next/navigation'\nimport { isBaseAuthRoute, isInternalRoute, isAuthRoute} from '../app-router/route-handler/internal-route'\nimport { hasRedirectLoop } from '../utils/construct'\n\n\n\n/**\n * @internal\n * Internal provider props - not meant for direct usage\n */\ninterface TernSecureClientProviderProps {\n children: React.ReactNode\n /** Callback when user state changes */\n onUserChanged?: (user: User | null) => Promise<void>\n /** Login page path */\n loginPath?: string\n /** Signup page path */\n signUpPath?: string\n /** Custom loading component */\n loadingComponent?: React.ReactNode\n /** Whether email verification is required */\n requiresVerification: boolean\n}\n\n/**\n * @internal\n * Internal provider component that handles authentication state\n * This is wrapped by the public TernSecureProvider\n */\n\nexport function TernSecureClientProvider({ \n children, \n loginPath = process.env.NEXT_PUBLIC_SIGN_IN_PATH || '/sign-in',\n signUpPath = process.env.NEXT_PUBLIC_SIGN_UP_PATH || '/sign-up',\n loadingComponent,\n requiresVerification,\n}: TernSecureClientProviderProps) {\n const auth = useMemo(() => ternSecureAuth, []);\n const router = useRouter();\n const pathname = usePathname() // Get current pathname\n const [isRedirecting, setIsRedirecting] = useState(false)\n\n const [authState, setAuthState] = useState<TernSecureState>(() => ({\n userId: null,\n isLoaded: false,\n error: null,\n isValid: false,\n isVerified: false,\n isAuthenticated: false,\n token: null,\n email: null,\n status: \"loading\",\n requiresVerification,\n }));\n\n const constructUrlWithRedirect = useCallback(\n (loginPath: string, currentPath: string, loginPathParam: string, signUpPathParam: string): string => {\n const baseUrl = window.location.origin\n const signInUrl = new URL(loginPath, baseUrl)\n\n // Only add redirect if not already on login or signup page\n if (!currentPath.includes(loginPathParam) && !currentPath.includes(signUpPathParam)) {\n signInUrl.searchParams.set(\"redirect\", currentPath)\n }\n return signInUrl.toString()\n },\n [],\n )\n\n\n const shouldRedirect = useCallback(\n (pathname: string, isVerified: boolean) => {\n // Get current search params\n const searchParams = new URLSearchParams(window.location.search)\n\n // Don't redirect if we're on the base sign-in page with no redirect param\n if (isBaseAuthRoute(pathname) && !searchParams.has(\"redirect\")) {\n return false\n }\n\n // Don't redirect if we're on an internal route\n if (isInternalRoute(pathname)) {\n return false\n }\n\n // Don't redirect if we're in auth routes (except when handling verification)\n if (isAuthRoute(pathname) && (!requiresVerification || isVerified)) {\n return false\n }\n\n return true\n },\n [requiresVerification],\n )\n\n const redirectToLogin = useCallback(\n (currentPath?: string) => {\n const path = currentPath || pathname || \"/\"\n\n\n if (isInternalRoute(path)) { // Don't redirect if we're already on an internal route\n return\n }\n\n // Check for redirect loops\n if (hasRedirectLoop(path, loginPath)) {\n return\n }\n\n setIsRedirecting(true)\n\n const loginUrl = constructUrlWithRedirect(loginPath, path, loginPath, signUpPath)\n\n if (process.env.NODE_ENV === \"production\") {\n window.location.href = loginUrl\n } else {\n // Use router.push for development\n router.push(loginUrl)\n }\n }, \n [router, loginPath, signUpPath, pathname, constructUrlWithRedirect]\n)\n\n const handleSignOut = useCallback(async (error?: Error) => {\n const currentPath = window.location.pathname\n await auth.signOut();\n setAuthState({\n isLoaded: true,\n userId: null,\n error: error || null,\n isValid: false,\n token: null,\n email: null,\n isVerified: false,\n isAuthenticated: false,\n status: \"unauthenticated\",\n requiresVerification,\n })\n redirectToLogin(currentPath)\n }, [auth, redirectToLogin, requiresVerification])\n\n const setEmail = useCallback((email: string) => {\n setAuthState((prev) => ({\n ...prev,\n email,\n }))\n }, [])\n\n const getAuthError = useCallback((): SignInResponse => {\n if (authState.error) {\n const error = authState.error as AuthError;\n return {\n success: false,\n message: error.message,\n error: error.code as keyof typeof ERRORS,\n user: null,\n }\n }\n\n if (authState.requiresVerification && authState.isValid && !authState.isVerified) {\n return {\n success: false,\n message: 'Email verification required',\n error: 'EMAIL_NOT_VERIFIED',\n user: null,\n }\n }\n\n if (!authState.isAuthenticated && authState.status !== \"loading\") {\n return {\n success: false,\n message: 'User is not authenticated',\n error: 'AUTHENTICATED',\n user: null,\n }\n }\n\n return {\n success: true,\n user: ternSecureAuth.currentUser,\n }\n }, [\n authState.error,\n authState.isValid,\n authState.isVerified,\n authState.isAuthenticated,\n authState.status,\n authState.requiresVerification,\n ])\n\nuseEffect(() => {\n let mounted = true\n let initialLoad = true\n\n const unsubscribe = onAuthStateChanged(\n auth,\n async (user: User | null) => {\n if (!mounted) return\n try {\n if (user) {\n const isValid = !!user.uid;\n const isVerified = user.emailVerified;\n const isAuthenticated = isValid && (!requiresVerification || isVerified) // Consider user authenticated if verification is not required or if email is verified\n\n setAuthState({\n isLoaded: true,\n userId: user.uid,\n isValid,\n isVerified,\n isAuthenticated: isValid && isVerified,\n token: user.getIdToken(),\n error: null,\n email: user.email,\n status: isAuthenticated ? \"authenticated\" : \"unverified\",\n requiresVerification,\n })\n \n if (requiresVerification && !isVerified && shouldRedirect(pathname || \"\", isVerified)) {\n if(initialLoad || !isRedirecting) {\n redirectToLogin(pathname)\n }\n }\n } else {\n setAuthState({\n isLoaded: true,\n userId: null,\n isValid: false,\n isVerified: false,\n isAuthenticated: false,\n token: null,\n error: null,\n email: null,\n status: \"unauthenticated\",\n requiresVerification,\n })\n \n if (shouldRedirect(pathname || \"\", false) && initialLoad) {\n redirectToLogin()\n }\n }\n } catch (error){\n console.error(\"Auth state change error:\", error)\n if (mounted) {\n handleSignOut(error instanceof Error ? error : new Error(\"Authentication error occurred\"))\n }\n } finally {\n initialLoad = false\n }\n })\n \n return () => {\n mounted = false\n unsubscribe()\n }\n }, [auth, handleSignOut, redirectToLogin, requiresVerification, pathname, isRedirecting, shouldRedirect])\n\n const contextValue: TernSecureCtxValue = useMemo(() => ({\n ...authState,\n signOut: handleSignOut,\n setEmail,\n getAuthError,\n redirectToLogin,\n }), [authState, handleSignOut, setEmail, getAuthError, redirectToLogin]);\n\n if (!authState.isLoaded) {\n return (\n <TernSecureCtx.Provider value={contextValue}>\n {loadingComponent || (\n <div aria-live=\"polite\" aria-busy=\"true\">\n <span className=\"sr-only\">Loading authentication state...</span>\n </div>\n )}\n </TernSecureCtx.Provider>\n );\n }\n\n return (\n <TernSecureCtx.Provider value={contextValue}>\n {children}\n </TernSecureCtx.Provider>\n )\n}"],"mappings":"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","names":["loginPath","pathname"]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/boundary/TernSecureCtx.tsx"],"sourcesContent":["\"use client\"\n\nimport { createContext, useContext } from 'react'\nimport { ternSecureAuth } from '../utils/client-init';\nimport { User } from 'firebase/auth';\nimport { type TernSecureState } from '../types';\n\nexport const TernSecureUser = (): User | null => {\n return ternSecureAuth.currentUser;\n}\n\nexport interface TernSecureCtxValue extends TernSecureState {\n signOut: () => Promise<void>\n setEmail: (email: string) => void\n}\n\nexport const TernSecureCtx = createContext<TernSecureCtxValue | null>(null)\n\nTernSecureCtx.displayName = 'TernSecureCtx'\n\nexport const useTernSecure = (hookName: string) => {\n const context = useContext(TernSecureCtx)\n \n if (!context) {\n throw new Error(\n `${hookName} must be used within TernSecureProvider`\n )\n }\n\n return context\n}\n\n"],"mappings":";AAEA,SAAS,eAAe,kBAAkB;AAC1C,SAAS,sBAAsB;AAIxB,MAAM,iBAAiB,MAAmB;AAC/C,SAAO,eAAe;AACxB;AAOO,MAAM,gBAAgB,cAAyC,IAAI;AAE1E,cAAc,cAAc;AAErB,MAAM,gBAAgB,CAAC,aAAqB;AACjD,QAAM,UAAU,WAAW,aAAa;AAExC,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI;AAAA,MACR,GAAG,QAAQ;AAAA,IACb;AAAA,EACF;AAEA,SAAO;AACT;","names":[]}
1
+ {"version":3,"sources":["../../../src/boundary/TernSecureCtx.tsx"],"sourcesContent":["\"use client\"\n\nimport { createContext, useContext } from 'react'\nimport { ternSecureAuth } from '../utils/client-init';\nimport { User } from 'firebase/auth';\nimport type { TernSecureState, SignInResponse } from '../types';\n\nexport const TernSecureUser = (): User | null => {\n return ternSecureAuth.currentUser;\n}\n\nexport interface TernSecureCtxValue extends TernSecureState {\n signOut: () => Promise<void>\n setEmail: (email: string) => void\n getAuthError: () => SignInResponse\n redirectToLogin: () => void\n}\n\nexport const TernSecureCtx = createContext<TernSecureCtxValue | null>(null)\n\nTernSecureCtx.displayName = 'TernSecureCtx'\n\nexport const useTernSecure = (hookName: string) => {\n const context = useContext(TernSecureCtx)\n \n if (!context) {\n throw new Error(\n `${hookName} must be used within TernSecureProvider`\n )\n }\n\n return context\n}\n\n"],"mappings":";AAEA,SAAS,eAAe,kBAAkB;AAC1C,SAAS,sBAAsB;AAIxB,MAAM,iBAAiB,MAAmB;AAC/C,SAAO,eAAe;AACxB;AASO,MAAM,gBAAgB,cAAyC,IAAI;AAE1E,cAAc,cAAc;AAErB,MAAM,gBAAgB,CAAC,aAAqB;AACjD,QAAM,UAAU,WAAW,aAAa;AAExC,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI;AAAA,MACR,GAAG,QAAQ;AAAA,IACb;AAAA,EACF;AAEA,SAAO;AACT;","names":[]}
@@ -10,21 +10,18 @@ function useAuth() {
10
10
  isVerified,
11
11
  isAuthenticated,
12
12
  token,
13
+ getAuthError,
14
+ status,
15
+ requiresVerification,
13
16
  signOut
14
17
  } = useTernSecure("useAuth");
15
18
  const user = TernSecureUser();
16
- const getAuthError = () => {
17
- if (error) return error;
18
- if (isValid && !isVerified) {
19
- return new Error("Email verification required");
20
- }
21
- return null;
22
- };
19
+ const authResponse = getAuthError();
23
20
  return {
24
21
  user,
25
22
  userId,
26
23
  isLoaded,
27
- error: getAuthError(),
24
+ error: authResponse.success ? null : authResponse,
28
25
  isValid,
29
26
  // User is signed in
30
27
  isVerified,
@@ -32,6 +29,8 @@ function useAuth() {
32
29
  isAuthenticated,
33
30
  // User is both signed in and verified
34
31
  token,
32
+ status,
33
+ requiresVerification,
35
34
  signOut
36
35
  };
37
36
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/boundary/hooks/useAuth.ts"],"sourcesContent":["\"use client\"\n\nimport { useTernSecure } from '../TernSecureCtx'\nimport { User } from 'firebase/auth'\nimport { TernSecureUser } from '../TernSecureCtx'\n\nexport function useAuth() {\n const {\n userId,\n isLoaded,\n error,\n isValid,\n isVerified,\n isAuthenticated,\n token,\n signOut\n } = useTernSecure('useAuth')\n\n const user: User | null = TernSecureUser()\n\n const getAuthError = () => {\n if (error) return error\n if (isValid && !isVerified) {\n return new Error('Email verification required')\n }\n return null\n }\n\n return {\n user,\n userId,\n isLoaded,\n error: getAuthError(),\n isValid, // User is signed in\n isVerified, // Email is verified\n isAuthenticated, // User is both signed in and verified\n token,\n signOut\n }\n}\n"],"mappings":";AAEA,SAAS,qBAAqB;AAE9B,SAAS,sBAAsB;AAExB,SAAS,UAAU;AACxB,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI,cAAc,SAAS;AAE3B,QAAM,OAAoB,eAAe;AAEzC,QAAM,eAAe,MAAM;AACzB,QAAI,MAAO,QAAO;AAClB,QAAI,WAAW,CAAC,YAAY;AAC1B,aAAO,IAAI,MAAM,6BAA6B;AAAA,IAChD;AACA,WAAO;AAAA,EACT;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA,OAAO,aAAa;AAAA,IACpB;AAAA;AAAA,IACA;AAAA;AAAA,IACA;AAAA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;","names":[]}
1
+ {"version":3,"sources":["../../../../src/boundary/hooks/useAuth.ts"],"sourcesContent":["\"use client\"\n\nimport { useTernSecure } from '../TernSecureCtx'\nimport { User } from 'firebase/auth'\nimport { TernSecureUser } from '../TernSecureCtx'\nimport type { SignInResponse } from '../../types'\n\n\nexport function useAuth() {\n const {\n userId,\n isLoaded,\n error,\n isValid,\n isVerified,\n isAuthenticated,\n token,\n getAuthError,\n status,\n requiresVerification,\n signOut\n } = useTernSecure('useAuth')\n\n const user: User | null = TernSecureUser()\n const authResponse: SignInResponse = getAuthError()\n\n\n return {\n user,\n userId,\n isLoaded,\n error: authResponse.success ? null : authResponse,\n isValid, // User is signed in\n isVerified, // Email is verified\n isAuthenticated, // User is both signed in and verified\n token,\n status,\n requiresVerification,\n signOut\n }\n}\n"],"mappings":";AAEA,SAAS,qBAAqB;AAE9B,SAAS,sBAAsB;AAIxB,SAAS,UAAU;AACxB,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI,cAAc,SAAS;AAE3B,QAAM,OAAoB,eAAe;AACzC,QAAM,eAA+B,aAAa;AAGlD,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA,OAAO,aAAa,UAAU,OAAO;AAAA,IACrC;AAAA;AAAA,IACA;AAAA;AAAA,IACA;AAAA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;","names":[]}