@lemondouble/lemon-auth 0.3.2 → 0.5.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/README.md CHANGED
@@ -288,7 +288,9 @@ export default async function RootLayout({ children }: { children: React.ReactNo
288
288
  <html lang="ko">
289
289
  <body>
290
290
  <AuthProvider user={user}>
291
- <AutoTokenRefresh />
291
+ <AutoTokenRefresh fallback={<p>세션 복원 중...</p>}>
292
+ <p>로그인이 필요합니다</p>
293
+ </AutoTokenRefresh>
292
294
  {children}
293
295
  </AuthProvider>
294
296
  </body>
@@ -297,21 +299,19 @@ export default async function RootLayout({ children }: { children: React.ReactNo
297
299
  }
298
300
  ```
299
301
 
300
- `children`을 전달하면 갱신 실패 시 (미인증 상태일 때) fallback UI를 렌더합니다:
301
-
302
- ```tsx
303
- <AutoTokenRefresh>
304
- <p>로그인 정보를 확인하는 중...</p>
305
- </AutoTokenRefresh>
306
- ```
302
+ | prop | 타입 | 필수 | 설명 |
303
+ |------|------|------|------|
304
+ | `fallback` | `React.ReactNode` | **필수** | 토큰 갱신 시도 중 렌더할 UI (스피너 등) |
305
+ | `children` | `React.ReactNode` | 선택 | 갱신 실패 시 렌더할 UI (로그인 버튼 등) |
307
306
 
308
307
  **동작 방식:**
309
308
 
310
309
  | 상황 | user | AutoTokenRefresh |
311
310
  |------|------|-----------------|
312
311
  | 정상 요청 (proxy 실행됨) | 있음 | 아무것도 안 함 (skip) |
312
+ | PWA 캐시 히트 + 갱신 시도 중 | 없음 | `fallback` 렌더 |
313
313
  | PWA 캐시 히트 + 유효한 refresh_token | 없음 | 갱신 → `router.refresh()` |
314
- | PWA 캐시 히트 + 만료된 refresh_token | 없음 | 갱신 실패 → children 렌더 |
314
+ | PWA 캐시 히트 + 만료된 refresh_token | 없음 | 갱신 실패 → `children` 렌더 |
315
315
 
316
316
  ### `loginUrl(redirectUrl)`
317
317
 
@@ -465,7 +465,7 @@ export default async function RootLayout({ children }: { children: React.ReactNo
465
465
  <html lang="ko">
466
466
  <body>
467
467
  <AuthProvider user={user}>
468
- <AutoTokenRefresh />
468
+ <AutoTokenRefresh fallback={<p>세션 복원 중...</p>} />
469
469
  {children}
470
470
  </AuthProvider>
471
471
  </body>
@@ -6,8 +6,8 @@ var REFRESH_URL = `${AUTH_SERVER_URL}/api/token/refresh`;
6
6
  var LOGOUT_URL = `${AUTH_SERVER_URL}/api/token/logout`;
7
7
  var PROFILE_URL = `${AUTH_SERVER_URL}/api/user/me`;
8
8
  var PROFILE_PAGE_URL = `${AUTH_SERVER_URL}/profile`;
9
- var ACCESS_TOKEN_COOKIE = "access_token";
10
- var REFRESH_TOKEN_COOKIE = "refresh_token";
9
+ var ACCESS_TOKEN_COOKIE = "lemon_access_token";
10
+ var REFRESH_TOKEN_COOKIE = "lemon_refresh_token";
11
11
 
12
12
  export {
13
13
  AUTH_SERVER_URL,
@@ -20,4 +20,4 @@ export {
20
20
  ACCESS_TOKEN_COOKIE,
21
21
  REFRESH_TOKEN_COOKIE
22
22
  };
23
- //# sourceMappingURL=chunk-B2EFAFKY.js.map
23
+ //# sourceMappingURL=chunk-3ARMB6QF.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/constants.ts"],"sourcesContent":["export const AUTH_SERVER_URL = \"https://auth.lemondouble.com\";\nexport const JWKS_URL = `${AUTH_SERVER_URL}/.well-known/jwks.json`;\nexport const LOGIN_URL = `${AUTH_SERVER_URL}/api/oauth2/google/login`;\nexport const REFRESH_URL = `${AUTH_SERVER_URL}/api/token/refresh`;\nexport const LOGOUT_URL = `${AUTH_SERVER_URL}/api/token/logout`;\nexport const PROFILE_URL = `${AUTH_SERVER_URL}/api/user/me`;\nexport const PROFILE_PAGE_URL = `${AUTH_SERVER_URL}/profile`;\n\nexport const ACCESS_TOKEN_COOKIE = \"access_token\";\nexport const REFRESH_TOKEN_COOKIE = \"refresh_token\";\n"],"mappings":";AAAO,IAAM,kBAAkB;AACxB,IAAM,WAAW,GAAG,eAAe;AACnC,IAAM,YAAY,GAAG,eAAe;AACpC,IAAM,cAAc,GAAG,eAAe;AACtC,IAAM,aAAa,GAAG,eAAe;AACrC,IAAM,cAAc,GAAG,eAAe;AACtC,IAAM,mBAAmB,GAAG,eAAe;AAE3C,IAAM,sBAAsB;AAC5B,IAAM,uBAAuB;","names":[]}
1
+ {"version":3,"sources":["../src/constants.ts"],"sourcesContent":["export const AUTH_SERVER_URL = \"https://auth.lemondouble.com\";\nexport const JWKS_URL = `${AUTH_SERVER_URL}/.well-known/jwks.json`;\nexport const LOGIN_URL = `${AUTH_SERVER_URL}/api/oauth2/google/login`;\nexport const REFRESH_URL = `${AUTH_SERVER_URL}/api/token/refresh`;\nexport const LOGOUT_URL = `${AUTH_SERVER_URL}/api/token/logout`;\nexport const PROFILE_URL = `${AUTH_SERVER_URL}/api/user/me`;\nexport const PROFILE_PAGE_URL = `${AUTH_SERVER_URL}/profile`;\n\nexport const ACCESS_TOKEN_COOKIE = \"lemon_access_token\";\nexport const REFRESH_TOKEN_COOKIE = \"lemon_refresh_token\";\n"],"mappings":";AAAO,IAAM,kBAAkB;AACxB,IAAM,WAAW,GAAG,eAAe;AACnC,IAAM,YAAY,GAAG,eAAe;AACpC,IAAM,cAAc,GAAG,eAAe;AACtC,IAAM,aAAa,GAAG,eAAe;AACrC,IAAM,cAAc,GAAG,eAAe;AACtC,IAAM,mBAAmB,GAAG,eAAe;AAE3C,IAAM,sBAAsB;AAC5B,IAAM,uBAAuB;","names":[]}
@@ -13,8 +13,9 @@ declare function AuthProvider({ user, children, }: {
13
13
  }): react_jsx_runtime.JSX.Element;
14
14
  declare function useAuth(): AuthContextValue;
15
15
 
16
- declare function AutoTokenRefresh({ children }: {
16
+ declare function AutoTokenRefresh({ children, fallback, }: {
17
17
  children?: React.ReactNode;
18
+ fallback: React.ReactNode;
18
19
  }): string | number | bigint | boolean | react.ReactElement<unknown, string | react.JSXElementConstructor<any>> | Iterable<react.ReactNode> | Promise<string | number | bigint | boolean | react.ReactPortal | react.ReactElement<unknown, string | react.JSXElementConstructor<any>> | Iterable<react.ReactNode> | null | undefined> | null;
19
20
 
20
21
  declare function loginUrl(redirectUrl: string): string;
@@ -4,7 +4,7 @@ import {
4
4
  LOGOUT_URL,
5
5
  PROFILE_PAGE_URL,
6
6
  REFRESH_URL
7
- } from "../chunk-B2EFAFKY.js";
7
+ } from "../chunk-3ARMB6QF.js";
8
8
 
9
9
  // src/client/context.tsx
10
10
  import { createContext, useContext } from "react";
@@ -24,7 +24,7 @@ function useAuth() {
24
24
  }
25
25
 
26
26
  // src/client/auto-token-refresh.tsx
27
- import { useEffect } from "react";
27
+ import { useEffect, useState } from "react";
28
28
  import { useRouter } from "next/navigation";
29
29
 
30
30
  // src/client/url.ts
@@ -52,15 +52,22 @@ async function refreshToken() {
52
52
  }
53
53
 
54
54
  // src/client/auto-token-refresh.tsx
55
- function AutoTokenRefresh({ children }) {
55
+ function AutoTokenRefresh({
56
+ children,
57
+ fallback
58
+ }) {
56
59
  const { isAuthenticated } = useAuth();
57
60
  const router = useRouter();
61
+ const [status, setStatus] = useState("loading");
58
62
  useEffect(() => {
59
63
  if (isAuthenticated) return;
60
64
  let ignore = false;
61
65
  refreshToken().then((ok) => {
62
- if (!ignore && ok) {
66
+ if (ignore) return;
67
+ if (ok) {
63
68
  router.refresh();
69
+ } else {
70
+ setStatus("failed");
64
71
  }
65
72
  });
66
73
  return () => {
@@ -68,6 +75,7 @@ function AutoTokenRefresh({ children }) {
68
75
  };
69
76
  }, [isAuthenticated, router]);
70
77
  if (isAuthenticated) return null;
78
+ if (status === "loading") return fallback ?? null;
71
79
  return children ?? null;
72
80
  }
73
81
  export {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/client/context.tsx","../../src/client/auto-token-refresh.tsx","../../src/client/url.ts"],"sourcesContent":["\"use client\";\n\nimport { createContext, useContext } from \"react\";\nimport type { LemonUser } from \"../types.js\";\n\ninterface AuthContextValue {\n user: LemonUser | null;\n isAuthenticated: boolean;\n}\n\nconst AuthContext = createContext<AuthContextValue>({\n user: null,\n isAuthenticated: false,\n});\n\nexport function AuthProvider({\n user,\n children,\n}: {\n user: LemonUser | null;\n children: React.ReactNode;\n}) {\n return (\n <AuthContext value={{ user, isAuthenticated: user !== null }}>\n {children}\n </AuthContext>\n );\n}\n\nexport function useAuth(): AuthContextValue {\n return useContext(AuthContext);\n}\n","\"use client\";\n\nimport { useEffect } from \"react\";\nimport { useRouter } from \"next/navigation\";\nimport { useAuth } from \"./context.js\";\nimport { refreshToken } from \"./url.js\";\n\nexport function AutoTokenRefresh({ children }: { children?: React.ReactNode }) {\n const { isAuthenticated } = useAuth();\n const router = useRouter();\n\n useEffect(() => {\n if (isAuthenticated) return;\n\n let ignore = false;\n\n refreshToken().then((ok) => {\n if (!ignore && ok) {\n router.refresh();\n }\n });\n\n return () => {\n ignore = true;\n };\n }, [isAuthenticated, router]);\n\n if (isAuthenticated) return null;\n return children ?? null;\n}\n","import {\n LOGIN_URL,\n LOGOUT_URL,\n PROFILE_PAGE_URL,\n REFRESH_URL,\n} from \"../constants.js\";\n\nexport function loginUrl(redirectUrl: string): string {\n return `${LOGIN_URL}?redirect_url=${encodeURIComponent(redirectUrl)}`;\n}\n\nexport function profileUrl(redirectUrl?: string): string {\n if (redirectUrl) {\n return `${PROFILE_PAGE_URL}?redirect_url=${encodeURIComponent(redirectUrl)}`;\n }\n return PROFILE_PAGE_URL;\n}\n\nexport async function logout(): Promise<void> {\n await fetch(LOGOUT_URL, {\n method: \"POST\",\n credentials: \"include\",\n });\n}\n\nexport async function refreshToken(): Promise<boolean> {\n const res = await fetch(REFRESH_URL, {\n method: \"POST\",\n credentials: \"include\",\n });\n return res.ok;\n}\n"],"mappings":";;;;;;;;AAEA,SAAS,eAAe,kBAAkB;AAqBtC;AAbJ,IAAM,cAAc,cAAgC;AAAA,EAClD,MAAM;AAAA,EACN,iBAAiB;AACnB,CAAC;AAEM,SAAS,aAAa;AAAA,EAC3B;AAAA,EACA;AACF,GAGG;AACD,SACE,oBAAC,eAAY,OAAO,EAAE,MAAM,iBAAiB,SAAS,KAAK,GACxD,UACH;AAEJ;AAEO,SAAS,UAA4B;AAC1C,SAAO,WAAW,WAAW;AAC/B;;;AC7BA,SAAS,iBAAiB;AAC1B,SAAS,iBAAiB;;;ACInB,SAAS,SAAS,aAA6B;AACpD,SAAO,GAAG,SAAS,iBAAiB,mBAAmB,WAAW,CAAC;AACrE;AAEO,SAAS,WAAW,aAA8B;AACvD,MAAI,aAAa;AACf,WAAO,GAAG,gBAAgB,iBAAiB,mBAAmB,WAAW,CAAC;AAAA,EAC5E;AACA,SAAO;AACT;AAEA,eAAsB,SAAwB;AAC5C,QAAM,MAAM,YAAY;AAAA,IACtB,QAAQ;AAAA,IACR,aAAa;AAAA,EACf,CAAC;AACH;AAEA,eAAsB,eAAiC;AACrD,QAAM,MAAM,MAAM,MAAM,aAAa;AAAA,IACnC,QAAQ;AAAA,IACR,aAAa;AAAA,EACf,CAAC;AACD,SAAO,IAAI;AACb;;;ADxBO,SAAS,iBAAiB,EAAE,SAAS,GAAmC;AAC7E,QAAM,EAAE,gBAAgB,IAAI,QAAQ;AACpC,QAAM,SAAS,UAAU;AAEzB,YAAU,MAAM;AACd,QAAI,gBAAiB;AAErB,QAAI,SAAS;AAEb,iBAAa,EAAE,KAAK,CAAC,OAAO;AAC1B,UAAI,CAAC,UAAU,IAAI;AACjB,eAAO,QAAQ;AAAA,MACjB;AAAA,IACF,CAAC;AAED,WAAO,MAAM;AACX,eAAS;AAAA,IACX;AAAA,EACF,GAAG,CAAC,iBAAiB,MAAM,CAAC;AAE5B,MAAI,gBAAiB,QAAO;AAC5B,SAAO,YAAY;AACrB;","names":[]}
1
+ {"version":3,"sources":["../../src/client/context.tsx","../../src/client/auto-token-refresh.tsx","../../src/client/url.ts"],"sourcesContent":["\"use client\";\n\nimport { createContext, useContext } from \"react\";\nimport type { LemonUser } from \"../types.js\";\n\ninterface AuthContextValue {\n user: LemonUser | null;\n isAuthenticated: boolean;\n}\n\nconst AuthContext = createContext<AuthContextValue>({\n user: null,\n isAuthenticated: false,\n});\n\nexport function AuthProvider({\n user,\n children,\n}: {\n user: LemonUser | null;\n children: React.ReactNode;\n}) {\n return (\n <AuthContext value={{ user, isAuthenticated: user !== null }}>\n {children}\n </AuthContext>\n );\n}\n\nexport function useAuth(): AuthContextValue {\n return useContext(AuthContext);\n}\n","\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { useRouter } from \"next/navigation\";\nimport { useAuth } from \"./context.js\";\nimport { refreshToken } from \"./url.js\";\n\nexport function AutoTokenRefresh({\n children,\n fallback,\n}: {\n children?: React.ReactNode;\n fallback: React.ReactNode;\n}) {\n const { isAuthenticated } = useAuth();\n const router = useRouter();\n const [status, setStatus] = useState<\"loading\" | \"failed\">(\"loading\");\n\n useEffect(() => {\n if (isAuthenticated) return;\n\n let ignore = false;\n\n refreshToken().then((ok) => {\n if (ignore) return;\n if (ok) {\n router.refresh();\n } else {\n setStatus(\"failed\");\n }\n });\n\n return () => {\n ignore = true;\n };\n }, [isAuthenticated, router]);\n\n if (isAuthenticated) return null;\n if (status === \"loading\") return fallback ?? null;\n return children ?? null;\n}\n","import {\n LOGIN_URL,\n LOGOUT_URL,\n PROFILE_PAGE_URL,\n REFRESH_URL,\n} from \"../constants.js\";\n\nexport function loginUrl(redirectUrl: string): string {\n return `${LOGIN_URL}?redirect_url=${encodeURIComponent(redirectUrl)}`;\n}\n\nexport function profileUrl(redirectUrl?: string): string {\n if (redirectUrl) {\n return `${PROFILE_PAGE_URL}?redirect_url=${encodeURIComponent(redirectUrl)}`;\n }\n return PROFILE_PAGE_URL;\n}\n\nexport async function logout(): Promise<void> {\n await fetch(LOGOUT_URL, {\n method: \"POST\",\n credentials: \"include\",\n });\n}\n\nexport async function refreshToken(): Promise<boolean> {\n const res = await fetch(REFRESH_URL, {\n method: \"POST\",\n credentials: \"include\",\n });\n return res.ok;\n}\n"],"mappings":";;;;;;;;AAEA,SAAS,eAAe,kBAAkB;AAqBtC;AAbJ,IAAM,cAAc,cAAgC;AAAA,EAClD,MAAM;AAAA,EACN,iBAAiB;AACnB,CAAC;AAEM,SAAS,aAAa;AAAA,EAC3B;AAAA,EACA;AACF,GAGG;AACD,SACE,oBAAC,eAAY,OAAO,EAAE,MAAM,iBAAiB,SAAS,KAAK,GACxD,UACH;AAEJ;AAEO,SAAS,UAA4B;AAC1C,SAAO,WAAW,WAAW;AAC/B;;;AC7BA,SAAS,WAAW,gBAAgB;AACpC,SAAS,iBAAiB;;;ACInB,SAAS,SAAS,aAA6B;AACpD,SAAO,GAAG,SAAS,iBAAiB,mBAAmB,WAAW,CAAC;AACrE;AAEO,SAAS,WAAW,aAA8B;AACvD,MAAI,aAAa;AACf,WAAO,GAAG,gBAAgB,iBAAiB,mBAAmB,WAAW,CAAC;AAAA,EAC5E;AACA,SAAO;AACT;AAEA,eAAsB,SAAwB;AAC5C,QAAM,MAAM,YAAY;AAAA,IACtB,QAAQ;AAAA,IACR,aAAa;AAAA,EACf,CAAC;AACH;AAEA,eAAsB,eAAiC;AACrD,QAAM,MAAM,MAAM,MAAM,aAAa;AAAA,IACnC,QAAQ;AAAA,IACR,aAAa;AAAA,EACf,CAAC;AACD,SAAO,IAAI;AACb;;;ADxBO,SAAS,iBAAiB;AAAA,EAC/B;AAAA,EACA;AACF,GAGG;AACD,QAAM,EAAE,gBAAgB,IAAI,QAAQ;AACpC,QAAM,SAAS,UAAU;AACzB,QAAM,CAAC,QAAQ,SAAS,IAAI,SAA+B,SAAS;AAEpE,YAAU,MAAM;AACd,QAAI,gBAAiB;AAErB,QAAI,SAAS;AAEb,iBAAa,EAAE,KAAK,CAAC,OAAO;AAC1B,UAAI,OAAQ;AACZ,UAAI,IAAI;AACN,eAAO,QAAQ;AAAA,MACjB,OAAO;AACL,kBAAU,QAAQ;AAAA,MACpB;AAAA,IACF,CAAC;AAED,WAAO,MAAM;AACX,eAAS;AAAA,IACX;AAAA,EACF,GAAG,CAAC,iBAAiB,MAAM,CAAC;AAE5B,MAAI,gBAAiB,QAAO;AAC5B,MAAI,WAAW,UAAW,QAAO,YAAY;AAC7C,SAAO,YAAY;AACrB;","names":[]}
@@ -5,7 +5,7 @@ import {
5
5
  LOGIN_URL,
6
6
  REFRESH_TOKEN_COOKIE,
7
7
  REFRESH_URL
8
- } from "../chunk-B2EFAFKY.js";
8
+ } from "../chunk-3ARMB6QF.js";
9
9
 
10
10
  // src/proxy/index.ts
11
11
  import { NextResponse } from "next/server";
@@ -94,7 +94,7 @@ async function tryRefresh(refreshToken) {
94
94
  try {
95
95
  const res = await fetch(REFRESH_URL, {
96
96
  method: "POST",
97
- headers: { Cookie: `refresh_token=${refreshToken}` }
97
+ headers: { Cookie: `lemon_refresh_token=${refreshToken}` }
98
98
  });
99
99
  if (!res.ok) return null;
100
100
  const setCookieHeaders = res.headers.getSetCookie();
@@ -107,7 +107,7 @@ async function tryRefresh(refreshToken) {
107
107
  }
108
108
  function extractAccessToken(setCookieHeaders) {
109
109
  for (const header of setCookieHeaders) {
110
- const match = header.match(/^access_token=([^;]+)/);
110
+ const match = header.match(/^lemon_access_token=([^;]+)/);
111
111
  if (match) return match[1];
112
112
  }
113
113
  return null;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/proxy/index.ts"],"sourcesContent":["import { NextRequest, NextResponse } from \"next/server\";\nimport {\n ACCESS_TOKEN_COOKIE,\n REFRESH_TOKEN_COOKIE,\n AUTH_SERVER_URL,\n REFRESH_URL,\n JWKS_URL,\n LOGIN_URL,\n} from \"../constants.js\";\nimport { createRemoteJWKSet, jwtVerify } from \"jose\";\nimport type { AccessTokenClaims } from \"../types.js\";\n\nconst REFRESH_BUFFER_SECONDS = 60;\n\nconst jwks = createRemoteJWKSet(new URL(JWKS_URL));\n\nexport interface AuthProxyOptions {\n publicPaths?: string[];\n clientId?: string;\n loginRedirectUrl?: string;\n onAuthSuccess?: (\n claims: AccessTokenClaims,\n request: NextRequest,\n response: NextResponse\n ) => Promise<NextResponse> | NextResponse;\n}\n\nexport function createAuthProxy(options: AuthProxyOptions = {}) {\n const { publicPaths = [], clientId, loginRedirectUrl, onAuthSuccess } =\n options;\n\n return async function proxy(request: NextRequest): Promise<NextResponse> {\n const { pathname } = request.nextUrl;\n\n if (isPublicPath(pathname, publicPaths)) {\n return maybeRefreshAndContinue(request);\n }\n\n const accessToken = request.cookies.get(ACCESS_TOKEN_COOKIE)?.value;\n const refreshToken = request.cookies.get(REFRESH_TOKEN_COOKIE)?.value;\n\n if (accessToken && isTokenFresh(accessToken)) {\n const claims = await verifyToken(accessToken);\n if (claims) {\n if (clientId && !claims.approved_clients.includes(clientId)) {\n return redirectToLogin(request, loginRedirectUrl);\n }\n const response = NextResponse.next();\n return onAuthSuccess\n ? await onAuthSuccess(claims, request, response)\n : response;\n }\n }\n\n if (refreshToken) {\n const result = await tryRefresh(refreshToken);\n if (result) {\n const claims = await verifyToken(result.newAccessToken);\n if (claims) {\n if (clientId && !claims.approved_clients.includes(clientId)) {\n return redirectToLogin(request, loginRedirectUrl);\n }\n const response = NextResponse.next();\n for (const header of result.setCookieHeaders) {\n response.headers.append(\"Set-Cookie\", header);\n }\n return onAuthSuccess\n ? await onAuthSuccess(claims, request, response)\n : response;\n }\n }\n }\n\n return redirectToLogin(request, loginRedirectUrl);\n };\n}\n\nasync function maybeRefreshAndContinue(\n request: NextRequest\n): Promise<NextResponse> {\n const accessToken = request.cookies.get(ACCESS_TOKEN_COOKIE)?.value;\n const refreshToken = request.cookies.get(REFRESH_TOKEN_COOKIE)?.value;\n\n if (accessToken && isTokenFresh(accessToken)) {\n return NextResponse.next();\n }\n\n if (refreshToken) {\n const result = await tryRefresh(refreshToken);\n if (result) {\n const response = NextResponse.next();\n for (const header of result.setCookieHeaders) {\n response.headers.append(\"Set-Cookie\", header);\n }\n return response;\n }\n }\n\n return NextResponse.next();\n}\n\nfunction isTokenFresh(token: string): boolean {\n try {\n const parts = token.split(\".\");\n if (parts.length !== 3) return false;\n const payload = JSON.parse(\n Buffer.from(parts[1], \"base64url\").toString()\n );\n return (\n typeof payload.exp === \"number\" &&\n payload.exp > Math.floor(Date.now() / 1000) + REFRESH_BUFFER_SECONDS\n );\n } catch {\n return false;\n }\n}\n\nasync function verifyToken(\n token: string\n): Promise<AccessTokenClaims | null> {\n try {\n const { payload } = await jwtVerify(token, jwks, {\n issuer: AUTH_SERVER_URL,\n });\n return payload as unknown as AccessTokenClaims;\n } catch {\n return null;\n }\n}\n\ninterface RefreshSuccess {\n newAccessToken: string;\n setCookieHeaders: string[];\n}\n\nasync function tryRefresh(\n refreshToken: string\n): Promise<RefreshSuccess | null> {\n try {\n const res = await fetch(REFRESH_URL, {\n method: \"POST\",\n headers: { Cookie: `refresh_token=${refreshToken}` },\n });\n\n if (!res.ok) return null;\n\n const setCookieHeaders = res.headers.getSetCookie();\n const newAccessToken = extractAccessToken(setCookieHeaders);\n if (!newAccessToken) return null;\n\n return { newAccessToken, setCookieHeaders };\n } catch {\n return null;\n }\n}\n\nfunction extractAccessToken(setCookieHeaders: string[]): string | null {\n for (const header of setCookieHeaders) {\n const match = header.match(/^access_token=([^;]+)/);\n if (match) return match[1];\n }\n return null;\n}\n\nfunction isPublicPath(pathname: string, publicPaths: string[]): boolean {\n return publicPaths.some((p) => {\n if (p.endsWith(\"*\")) {\n return pathname.startsWith(p.slice(0, -1));\n }\n return pathname === p;\n });\n}\n\nfunction redirectToLogin(\n request: NextRequest,\n loginRedirectUrl?: string\n): NextResponse {\n if (loginRedirectUrl) {\n const url = `${LOGIN_URL}?redirect_url=${encodeURIComponent(loginRedirectUrl)}`;\n return NextResponse.redirect(new URL(url, request.url));\n }\n return NextResponse.redirect(new URL(\"/\", request.url));\n}\n\nexport type { AccessTokenClaims } from \"../types.js\";\nexport type { LemonUser } from \"../types.js\";\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/proxy/index.ts"],"sourcesContent":["import { NextRequest, NextResponse } from \"next/server\";\nimport {\n ACCESS_TOKEN_COOKIE,\n REFRESH_TOKEN_COOKIE,\n AUTH_SERVER_URL,\n REFRESH_URL,\n JWKS_URL,\n LOGIN_URL,\n} from \"../constants.js\";\nimport { createRemoteJWKSet, jwtVerify } from \"jose\";\nimport type { AccessTokenClaims } from \"../types.js\";\n\nconst REFRESH_BUFFER_SECONDS = 60;\n\nconst jwks = createRemoteJWKSet(new URL(JWKS_URL));\n\nexport interface AuthProxyOptions {\n publicPaths?: string[];\n clientId?: string;\n loginRedirectUrl?: string;\n onAuthSuccess?: (\n claims: AccessTokenClaims,\n request: NextRequest,\n response: NextResponse\n ) => Promise<NextResponse> | NextResponse;\n}\n\nexport function createAuthProxy(options: AuthProxyOptions = {}) {\n const { publicPaths = [], clientId, loginRedirectUrl, onAuthSuccess } =\n options;\n\n return async function proxy(request: NextRequest): Promise<NextResponse> {\n const { pathname } = request.nextUrl;\n\n if (isPublicPath(pathname, publicPaths)) {\n return maybeRefreshAndContinue(request);\n }\n\n const accessToken = request.cookies.get(ACCESS_TOKEN_COOKIE)?.value;\n const refreshToken = request.cookies.get(REFRESH_TOKEN_COOKIE)?.value;\n\n if (accessToken && isTokenFresh(accessToken)) {\n const claims = await verifyToken(accessToken);\n if (claims) {\n if (clientId && !claims.approved_clients.includes(clientId)) {\n return redirectToLogin(request, loginRedirectUrl);\n }\n const response = NextResponse.next();\n return onAuthSuccess\n ? await onAuthSuccess(claims, request, response)\n : response;\n }\n }\n\n if (refreshToken) {\n const result = await tryRefresh(refreshToken);\n if (result) {\n const claims = await verifyToken(result.newAccessToken);\n if (claims) {\n if (clientId && !claims.approved_clients.includes(clientId)) {\n return redirectToLogin(request, loginRedirectUrl);\n }\n const response = NextResponse.next();\n for (const header of result.setCookieHeaders) {\n response.headers.append(\"Set-Cookie\", header);\n }\n return onAuthSuccess\n ? await onAuthSuccess(claims, request, response)\n : response;\n }\n }\n }\n\n return redirectToLogin(request, loginRedirectUrl);\n };\n}\n\nasync function maybeRefreshAndContinue(\n request: NextRequest\n): Promise<NextResponse> {\n const accessToken = request.cookies.get(ACCESS_TOKEN_COOKIE)?.value;\n const refreshToken = request.cookies.get(REFRESH_TOKEN_COOKIE)?.value;\n\n if (accessToken && isTokenFresh(accessToken)) {\n return NextResponse.next();\n }\n\n if (refreshToken) {\n const result = await tryRefresh(refreshToken);\n if (result) {\n const response = NextResponse.next();\n for (const header of result.setCookieHeaders) {\n response.headers.append(\"Set-Cookie\", header);\n }\n return response;\n }\n }\n\n return NextResponse.next();\n}\n\nfunction isTokenFresh(token: string): boolean {\n try {\n const parts = token.split(\".\");\n if (parts.length !== 3) return false;\n const payload = JSON.parse(\n Buffer.from(parts[1], \"base64url\").toString()\n );\n return (\n typeof payload.exp === \"number\" &&\n payload.exp > Math.floor(Date.now() / 1000) + REFRESH_BUFFER_SECONDS\n );\n } catch {\n return false;\n }\n}\n\nasync function verifyToken(\n token: string\n): Promise<AccessTokenClaims | null> {\n try {\n const { payload } = await jwtVerify(token, jwks, {\n issuer: AUTH_SERVER_URL,\n });\n return payload as unknown as AccessTokenClaims;\n } catch {\n return null;\n }\n}\n\ninterface RefreshSuccess {\n newAccessToken: string;\n setCookieHeaders: string[];\n}\n\nasync function tryRefresh(\n refreshToken: string\n): Promise<RefreshSuccess | null> {\n try {\n const res = await fetch(REFRESH_URL, {\n method: \"POST\",\n headers: { Cookie: `lemon_refresh_token=${refreshToken}` },\n });\n\n if (!res.ok) return null;\n\n const setCookieHeaders = res.headers.getSetCookie();\n const newAccessToken = extractAccessToken(setCookieHeaders);\n if (!newAccessToken) return null;\n\n return { newAccessToken, setCookieHeaders };\n } catch {\n return null;\n }\n}\n\nfunction extractAccessToken(setCookieHeaders: string[]): string | null {\n for (const header of setCookieHeaders) {\n const match = header.match(/^lemon_access_token=([^;]+)/);\n if (match) return match[1];\n }\n return null;\n}\n\nfunction isPublicPath(pathname: string, publicPaths: string[]): boolean {\n return publicPaths.some((p) => {\n if (p.endsWith(\"*\")) {\n return pathname.startsWith(p.slice(0, -1));\n }\n return pathname === p;\n });\n}\n\nfunction redirectToLogin(\n request: NextRequest,\n loginRedirectUrl?: string\n): NextResponse {\n if (loginRedirectUrl) {\n const url = `${LOGIN_URL}?redirect_url=${encodeURIComponent(loginRedirectUrl)}`;\n return NextResponse.redirect(new URL(url, request.url));\n }\n return NextResponse.redirect(new URL(\"/\", request.url));\n}\n\nexport type { AccessTokenClaims } from \"../types.js\";\nexport type { LemonUser } from \"../types.js\";\n"],"mappings":"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","names":[]}
@@ -22,7 +22,7 @@ declare const REFRESH_URL = "https://auth.lemondouble.com/api/token/refresh";
22
22
  declare const LOGOUT_URL = "https://auth.lemondouble.com/api/token/logout";
23
23
  declare const PROFILE_URL = "https://auth.lemondouble.com/api/user/me";
24
24
  declare const PROFILE_PAGE_URL = "https://auth.lemondouble.com/profile";
25
- declare const ACCESS_TOKEN_COOKIE = "access_token";
26
- declare const REFRESH_TOKEN_COOKIE = "refresh_token";
25
+ declare const ACCESS_TOKEN_COOKIE = "lemon_access_token";
26
+ declare const REFRESH_TOKEN_COOKIE = "lemon_refresh_token";
27
27
 
28
28
  export { ACCESS_TOKEN_COOKIE, AUTH_SERVER_URL, AccessTokenClaims, JWKS_URL, LOGIN_URL, LOGOUT_URL, LemonUser, PROFILE_PAGE_URL, PROFILE_URL, REFRESH_TOKEN_COOKIE, REFRESH_URL, type RefreshResult, getUser, refreshTokenFromCookie, requireAuth, requireClient, verifyAccessToken, verifyAccessTokenString };
@@ -8,7 +8,7 @@ import {
8
8
  PROFILE_URL,
9
9
  REFRESH_TOKEN_COOKIE,
10
10
  REFRESH_URL
11
- } from "../chunk-B2EFAFKY.js";
11
+ } from "../chunk-3ARMB6QF.js";
12
12
 
13
13
  // src/server/verify.ts
14
14
  import { createRemoteJWKSet, jwtVerify } from "jose";
@@ -73,7 +73,7 @@ async function refreshTokenFromCookie(refreshTokenCookie) {
73
73
  const res = await fetch(REFRESH_URL, {
74
74
  method: "POST",
75
75
  headers: {
76
- Cookie: `refresh_token=${refreshTokenCookie}`
76
+ Cookie: `lemon_refresh_token=${refreshTokenCookie}`
77
77
  }
78
78
  });
79
79
  if (!res.ok) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/server/verify.ts","../../src/server/user.ts","../../src/server/refresh.ts"],"sourcesContent":["import { createRemoteJWKSet, jwtVerify } from \"jose\";\nimport { cookies } from \"next/headers\";\nimport type { AccessTokenClaims } from \"../types.js\";\nimport { JWKS_URL, AUTH_SERVER_URL, ACCESS_TOKEN_COOKIE } from \"../constants.js\";\n\nconst jwks = createRemoteJWKSet(new URL(JWKS_URL));\n\nexport async function verifyAccessToken(): Promise<AccessTokenClaims | null> {\n const cookieStore = await cookies();\n const token = cookieStore.get(ACCESS_TOKEN_COOKIE)?.value;\n if (!token) return null;\n\n try {\n const { payload } = await jwtVerify(token, jwks, {\n issuer: AUTH_SERVER_URL,\n });\n return payload as unknown as AccessTokenClaims;\n } catch {\n return null;\n }\n}\n\nexport async function verifyAccessTokenString(\n token: string\n): Promise<AccessTokenClaims | null> {\n try {\n const { payload } = await jwtVerify(token, jwks, {\n issuer: AUTH_SERVER_URL,\n });\n return payload as unknown as AccessTokenClaims;\n } catch {\n return null;\n }\n}\n","import { cache } from \"react\";\nimport { redirect } from \"next/navigation\";\nimport type { LemonUser } from \"../types.js\";\nimport { verifyAccessToken } from \"./verify.js\";\n\nfunction claimsToUser(\n claims: NonNullable<Awaited<ReturnType<typeof verifyAccessToken>>>\n): LemonUser {\n return {\n uid: claims.sub,\n nickname: claims.nickname,\n profileImageUrl: claims.profile_image_url,\n role: claims.role,\n approvedClients: claims.approved_clients,\n };\n}\n\nexport const getUser = cache(async (): Promise<LemonUser | null> => {\n const claims = await verifyAccessToken();\n if (!claims) return null;\n return claimsToUser(claims);\n});\n\nexport async function requireAuth(redirectTo = \"/\"): Promise<LemonUser> {\n const user = await getUser();\n if (user) return user;\n redirect(redirectTo);\n}\n\nexport async function requireClient(\n clientId: string,\n redirectTo = \"/\"\n): Promise<LemonUser> {\n const user = await requireAuth(redirectTo);\n if (!user.approvedClients.includes(clientId)) {\n throw new Error(\"Client not approved\");\n }\n return user;\n}\n","import { REFRESH_URL } from \"../constants.js\";\n\nexport interface RefreshResult {\n ok: boolean;\n setCookieHeaders: string[];\n}\n\nexport async function refreshTokenFromCookie(\n refreshTokenCookie: string\n): Promise<RefreshResult> {\n const res = await fetch(REFRESH_URL, {\n method: \"POST\",\n headers: {\n Cookie: `refresh_token=${refreshTokenCookie}`,\n },\n });\n\n if (!res.ok) {\n return { ok: false, setCookieHeaders: [] };\n }\n\n const setCookieHeaders = res.headers.getSetCookie();\n return { ok: true, setCookieHeaders };\n}\n"],"mappings":";;;;;;;;;;;;;AAAA,SAAS,oBAAoB,iBAAiB;AAC9C,SAAS,eAAe;AAIxB,IAAM,OAAO,mBAAmB,IAAI,IAAI,QAAQ,CAAC;AAEjD,eAAsB,oBAAuD;AAC3E,QAAM,cAAc,MAAM,QAAQ;AAClC,QAAM,QAAQ,YAAY,IAAI,mBAAmB,GAAG;AACpD,MAAI,CAAC,MAAO,QAAO;AAEnB,MAAI;AACF,UAAM,EAAE,QAAQ,IAAI,MAAM,UAAU,OAAO,MAAM;AAAA,MAC/C,QAAQ;AAAA,IACV,CAAC;AACD,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,eAAsB,wBACpB,OACmC;AACnC,MAAI;AACF,UAAM,EAAE,QAAQ,IAAI,MAAM,UAAU,OAAO,MAAM;AAAA,MAC/C,QAAQ;AAAA,IACV,CAAC;AACD,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;;;ACjCA,SAAS,aAAa;AACtB,SAAS,gBAAgB;AAIzB,SAAS,aACP,QACW;AACX,SAAO;AAAA,IACL,KAAK,OAAO;AAAA,IACZ,UAAU,OAAO;AAAA,IACjB,iBAAiB,OAAO;AAAA,IACxB,MAAM,OAAO;AAAA,IACb,iBAAiB,OAAO;AAAA,EAC1B;AACF;AAEO,IAAM,UAAU,MAAM,YAAuC;AAClE,QAAM,SAAS,MAAM,kBAAkB;AACvC,MAAI,CAAC,OAAQ,QAAO;AACpB,SAAO,aAAa,MAAM;AAC5B,CAAC;AAED,eAAsB,YAAY,aAAa,KAAyB;AACtE,QAAM,OAAO,MAAM,QAAQ;AAC3B,MAAI,KAAM,QAAO;AACjB,WAAS,UAAU;AACrB;AAEA,eAAsB,cACpB,UACA,aAAa,KACO;AACpB,QAAM,OAAO,MAAM,YAAY,UAAU;AACzC,MAAI,CAAC,KAAK,gBAAgB,SAAS,QAAQ,GAAG;AAC5C,UAAM,IAAI,MAAM,qBAAqB;AAAA,EACvC;AACA,SAAO;AACT;;;AC/BA,eAAsB,uBACpB,oBACwB;AACxB,QAAM,MAAM,MAAM,MAAM,aAAa;AAAA,IACnC,QAAQ;AAAA,IACR,SAAS;AAAA,MACP,QAAQ,iBAAiB,kBAAkB;AAAA,IAC7C;AAAA,EACF,CAAC;AAED,MAAI,CAAC,IAAI,IAAI;AACX,WAAO,EAAE,IAAI,OAAO,kBAAkB,CAAC,EAAE;AAAA,EAC3C;AAEA,QAAM,mBAAmB,IAAI,QAAQ,aAAa;AAClD,SAAO,EAAE,IAAI,MAAM,iBAAiB;AACtC;","names":[]}
1
+ {"version":3,"sources":["../../src/server/verify.ts","../../src/server/user.ts","../../src/server/refresh.ts"],"sourcesContent":["import { createRemoteJWKSet, jwtVerify } from \"jose\";\nimport { cookies } from \"next/headers\";\nimport type { AccessTokenClaims } from \"../types.js\";\nimport { JWKS_URL, AUTH_SERVER_URL, ACCESS_TOKEN_COOKIE } from \"../constants.js\";\n\nconst jwks = createRemoteJWKSet(new URL(JWKS_URL));\n\nexport async function verifyAccessToken(): Promise<AccessTokenClaims | null> {\n const cookieStore = await cookies();\n const token = cookieStore.get(ACCESS_TOKEN_COOKIE)?.value;\n if (!token) return null;\n\n try {\n const { payload } = await jwtVerify(token, jwks, {\n issuer: AUTH_SERVER_URL,\n });\n return payload as unknown as AccessTokenClaims;\n } catch {\n return null;\n }\n}\n\nexport async function verifyAccessTokenString(\n token: string\n): Promise<AccessTokenClaims | null> {\n try {\n const { payload } = await jwtVerify(token, jwks, {\n issuer: AUTH_SERVER_URL,\n });\n return payload as unknown as AccessTokenClaims;\n } catch {\n return null;\n }\n}\n","import { cache } from \"react\";\nimport { redirect } from \"next/navigation\";\nimport type { LemonUser } from \"../types.js\";\nimport { verifyAccessToken } from \"./verify.js\";\n\nfunction claimsToUser(\n claims: NonNullable<Awaited<ReturnType<typeof verifyAccessToken>>>\n): LemonUser {\n return {\n uid: claims.sub,\n nickname: claims.nickname,\n profileImageUrl: claims.profile_image_url,\n role: claims.role,\n approvedClients: claims.approved_clients,\n };\n}\n\nexport const getUser = cache(async (): Promise<LemonUser | null> => {\n const claims = await verifyAccessToken();\n if (!claims) return null;\n return claimsToUser(claims);\n});\n\nexport async function requireAuth(redirectTo = \"/\"): Promise<LemonUser> {\n const user = await getUser();\n if (user) return user;\n redirect(redirectTo);\n}\n\nexport async function requireClient(\n clientId: string,\n redirectTo = \"/\"\n): Promise<LemonUser> {\n const user = await requireAuth(redirectTo);\n if (!user.approvedClients.includes(clientId)) {\n throw new Error(\"Client not approved\");\n }\n return user;\n}\n","import { REFRESH_URL } from \"../constants.js\";\n\nexport interface RefreshResult {\n ok: boolean;\n setCookieHeaders: string[];\n}\n\nexport async function refreshTokenFromCookie(\n refreshTokenCookie: string\n): Promise<RefreshResult> {\n const res = await fetch(REFRESH_URL, {\n method: \"POST\",\n headers: {\n Cookie: `lemon_refresh_token=${refreshTokenCookie}`,\n },\n });\n\n if (!res.ok) {\n return { ok: false, setCookieHeaders: [] };\n }\n\n const setCookieHeaders = res.headers.getSetCookie();\n return { ok: true, setCookieHeaders };\n}\n"],"mappings":";;;;;;;;;;;;;AAAA,SAAS,oBAAoB,iBAAiB;AAC9C,SAAS,eAAe;AAIxB,IAAM,OAAO,mBAAmB,IAAI,IAAI,QAAQ,CAAC;AAEjD,eAAsB,oBAAuD;AAC3E,QAAM,cAAc,MAAM,QAAQ;AAClC,QAAM,QAAQ,YAAY,IAAI,mBAAmB,GAAG;AACpD,MAAI,CAAC,MAAO,QAAO;AAEnB,MAAI;AACF,UAAM,EAAE,QAAQ,IAAI,MAAM,UAAU,OAAO,MAAM;AAAA,MAC/C,QAAQ;AAAA,IACV,CAAC;AACD,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,eAAsB,wBACpB,OACmC;AACnC,MAAI;AACF,UAAM,EAAE,QAAQ,IAAI,MAAM,UAAU,OAAO,MAAM;AAAA,MAC/C,QAAQ;AAAA,IACV,CAAC;AACD,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;;;ACjCA,SAAS,aAAa;AACtB,SAAS,gBAAgB;AAIzB,SAAS,aACP,QACW;AACX,SAAO;AAAA,IACL,KAAK,OAAO;AAAA,IACZ,UAAU,OAAO;AAAA,IACjB,iBAAiB,OAAO;AAAA,IACxB,MAAM,OAAO;AAAA,IACb,iBAAiB,OAAO;AAAA,EAC1B;AACF;AAEO,IAAM,UAAU,MAAM,YAAuC;AAClE,QAAM,SAAS,MAAM,kBAAkB;AACvC,MAAI,CAAC,OAAQ,QAAO;AACpB,SAAO,aAAa,MAAM;AAC5B,CAAC;AAED,eAAsB,YAAY,aAAa,KAAyB;AACtE,QAAM,OAAO,MAAM,QAAQ;AAC3B,MAAI,KAAM,QAAO;AACjB,WAAS,UAAU;AACrB;AAEA,eAAsB,cACpB,UACA,aAAa,KACO;AACpB,QAAM,OAAO,MAAM,YAAY,UAAU;AACzC,MAAI,CAAC,KAAK,gBAAgB,SAAS,QAAQ,GAAG;AAC5C,UAAM,IAAI,MAAM,qBAAqB;AAAA,EACvC;AACA,SAAO;AACT;;;AC/BA,eAAsB,uBACpB,oBACwB;AACxB,QAAM,MAAM,MAAM,MAAM,aAAa;AAAA,IACnC,QAAQ;AAAA,IACR,SAAS;AAAA,MACP,QAAQ,uBAAuB,kBAAkB;AAAA,IACnD;AAAA,EACF,CAAC;AAED,MAAI,CAAC,IAAI,IAAI;AACX,WAAO,EAAE,IAAI,OAAO,kBAAkB,CAAC,EAAE;AAAA,EAC3C;AAEA,QAAM,mBAAmB,IAAI,QAAQ,aAAa;AAClD,SAAO,EAAE,IAAI,MAAM,iBAAiB;AACtC;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lemondouble/lemon-auth",
3
- "version": "0.3.2",
3
+ "version": "0.5.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/LemonDouble/lemon-auth"