@lemondouble/lemon-auth 0.9.0 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -48,7 +48,10 @@ var jwks = createRemoteJWKSet(new URL(JWKS_URL));
48
48
  async function verify(token) {
49
49
  try {
50
50
  const { payload } = await jwtVerify(token, jwks, {
51
- issuer: AUTH_SERVER_URL
51
+ issuer: AUTH_SERVER_URL,
52
+ // 발급자(자택 k3s)와 검증자(Vercel)의 시계가 몇 초 어긋나면, 방금
53
+ // 발급된 토큰이 nbf 미래로 거부되어 로그인 직후 로그아웃으로 보인다.
54
+ clockTolerance: 10
52
55
  });
53
56
  if (isAccessTokenClaims(payload)) return payload;
54
57
  warnAuth("access token claims shape mismatch");
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/server/verify.ts","../../src/claims.ts","../../src/log.ts","../../src/server/user.ts","../../src/approval.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\";\nimport { isAccessTokenClaims } from \"../claims.js\";\nimport { getMockAccessTokenClaims } from \"../mock.js\";\nimport { warnAuth, errorCode } from \"../log.js\";\n\nconst jwks = createRemoteJWKSet(new URL(JWKS_URL));\n\n/**\n * 검증과 진단을 한 곳에 모은다.\n *\n * 예전에는 아래 두 export 가 같은 try/catch 를 각자 갖고 있었다. 그러면\n * 로깅을 붙일 때도 벌마다 갈리고, 한쪽만 고치는 일이 생긴다.\n */\nasync function verify(token: string): Promise<AccessTokenClaims | null> {\n try {\n const { payload } = await jwtVerify(token, jwks, {\n issuer: AUTH_SERVER_URL,\n });\n if (isAccessTokenClaims(payload)) return payload;\n warnAuth(\"access token claims shape mismatch\");\n return null;\n } catch (error) {\n // 정상 만료는 갱신으로 이어지는 흔한 경로라 남기지 않는다. 나머지는\n // 원인이 전혀 다르다 — 특히 ERR_JWKS_NO_MATCHING_KEY 는 키 로테이션이나\n // JWKS 도달 실패이고, 이걸 위조 토큰과 뭉개면 전 사용자가 조용히\n // 로그아웃된 이유를 알 수 없게 된다.\n const code = errorCode(error);\n if (code !== \"ERR_JWT_EXPIRED\") {\n warnAuth(\"access token verification failed\", { code });\n }\n return null;\n }\n}\n\nexport async function verifyAccessToken(): Promise<AccessTokenClaims | null> {\n const mockClaims = getMockAccessTokenClaims();\n if (mockClaims) return mockClaims;\n\n const cookieStore = await cookies();\n const token = cookieStore.get(ACCESS_TOKEN_COOKIE)?.value;\n if (!token) return null;\n\n return verify(token);\n}\n\nexport async function verifyAccessTokenString(\n token: string\n): Promise<AccessTokenClaims | null> {\n const mockClaims = getMockAccessTokenClaims();\n if (mockClaims) return mockClaims;\n\n return verify(token);\n}\n","import type { JWTPayload } from \"jose\";\nimport type { AccessTokenClaims } from \"./types.js\";\n\nexport function isAccessTokenClaims(\n payload: JWTPayload\n): payload is AccessTokenClaims {\n return (\n payload.token_type === \"access\" &&\n typeof payload.sub === \"string\" &&\n typeof payload.nickname === \"string\" &&\n typeof payload.profile_image_url === \"string\" &&\n (payload.role === \"user\" || payload.role === \"admin\") &&\n Array.isArray(payload.approved_clients) &&\n payload.approved_clients.every((clientId) => typeof clientId === \"string\")\n );\n}\n","/**\n * 진단 로그.\n *\n * 이 라이브러리는 오랫동안 실패를 전부 `null` 하나로만 알렸다. 2026-07-19 에\n * auth 서버 앞의 Cloudflare Bot Fight Mode 가 프록시의 갱신 요청을 403\n * (`cf-mitigated: challenge`) 으로 막았을 때, 남은 흔적이 하나도 없어서\n * 원인을 찾는 데 반나절이 걸렸다. 앱 쪽에 임시 진단 코드를 심고 프로덕션에\n * 두 번 배포한 뒤에야 응답 코드를 볼 수 있었다.\n *\n * 그래서 실패에는 최소한의 사실을 남긴다.\n *\n * **토큰 값은 절대 남기지 않는다.** JWT 는 base64url 이라 \"접두 8자\" 같은\n * 안전해 보이는 일부만으로도 헤더와 payload 앞부분이 그대로 복원된다.\n * `sub`, `nickname`, `approved_clients` 가 거기 들어 있다. 남길 수 있는 것은\n * 쿠키 **이름**, HTTP 상태 코드, 에러 코드처럼 값이 아닌 사실뿐이다.\n */\n\nconst PREFIX = \"[lemon-auth]\";\n\nexport function warnAuth(\n event: string,\n detail?: Record<string, unknown>\n): void {\n if (detail === undefined) {\n console.warn(`${PREFIX} ${event}`);\n return;\n }\n console.warn(`${PREFIX} ${event}`, JSON.stringify(detail));\n}\n\n/**\n * 에러에서 값 노출 없이 식별자만 뽑는다.\n *\n * jose 는 `ERR_JWKS_NO_MATCHING_KEY`(키 로테이션), `ERR_JWT_EXPIRED`(정상 만료),\n * `ERR_JWS_SIGNATURE_VERIFICATION_FAILED`(위조 또는 설정 오류) 처럼 원인이\n * 전혀 다른 실패를 구분 가능한 `code` 로 준다. 이걸 뭉개면 JWKS 장애와\n * 위조 토큰이 같은 증상으로 보인다.\n *\n * undici 의 네트워크 실패는 `cause.code` 에 `ENOTFOUND` 같은 값이 들어간다.\n */\nexport function errorCode(error: unknown): string {\n if (typeof error === \"object\" && error !== null) {\n const { code, cause } = error as { code?: unknown; cause?: unknown };\n if (typeof code === \"string\") return code;\n if (typeof cause === \"object\" && cause !== null) {\n const causeCode = (cause as { code?: unknown }).code;\n if (typeof causeCode === \"string\") return causeCode;\n }\n }\n if (error instanceof Error) return error.name;\n return \"unknown\";\n}\n","import { cache } from \"react\";\nimport { redirect } from \"next/navigation\";\nimport type { LemonSession, LemonUser } from \"../types.js\";\nimport { AUTH_SERVER_URL } from \"../constants.js\";\nimport { isApprovedClient } from \"../approval.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 interface GetSessionOptions {\n clientId?: string;\n}\n\nconst getSessionByClientId = cache(\n async (clientId?: string): Promise<LemonSession> => {\n const user = await getUser();\n if (!user) return { type: \"none\" };\n if (!isApprovedClient(user.approvedClients, clientId)) {\n return { type: \"unapproved\", user };\n }\n return { type: \"authenticated\", user };\n }\n);\n\nexport async function getSession(\n options: GetSessionOptions = {}\n): Promise<LemonSession> {\n return getSessionByClientId(options.clientId);\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 interface RequireClientOptions {\n loginRedirectTo?: string;\n unapprovedRedirectTo?: string;\n}\n\nexport async function requireClient(\n clientId: string,\n optionsOrRedirectTo: string | RequireClientOptions = \"/\"\n): Promise<LemonUser> {\n const options =\n typeof optionsOrRedirectTo === \"string\"\n ? { loginRedirectTo: optionsOrRedirectTo }\n : optionsOrRedirectTo;\n const session = await getSession({ clientId });\n if (session.type === \"none\") {\n redirect(options.loginRedirectTo ?? \"/\");\n }\n if (session.type === \"unapproved\") {\n redirect(\n options.unapprovedRedirectTo ??\n `${AUTH_SERVER_URL}/error?code=FORBIDDEN`\n );\n }\n return session.user;\n}\n","import { warnAuth } from \"./log.js\";\n\n/** fail-open 경고를 요청마다 찍지 않기 위한 플래그. */\nlet warnedMissingClientId = false;\n\nexport function isApprovedClient(\n approvedClients: string[],\n clientId?: string\n): boolean {\n if (!clientId) {\n // clientId 를 안 넘기면 승인 검사가 통째로 열린다(fail-open). 이 라이브러리를\n // clientId 없이 쓰는 앱도 있으므로 동작은 유지하지만, 환경변수가 배포에서\n // 빠져서 이렇게 된 경우와 구분이 안 된다. 후자는 인증이 사실상 없는 상태라\n // 조용히 넘어가면 안 된다.\n if (!warnedMissingClientId) {\n warnedMissingClientId = true;\n warnAuth(\n \"clientId not provided — approval check is disabled (all users pass). \" +\n \"설정 누락이 아닌지 확인할 것.\"\n );\n }\n return true;\n }\n return approvedClients.includes(clientId) || approvedClients.includes(\"*\");\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/server/verify.ts","../../src/claims.ts","../../src/log.ts","../../src/server/user.ts","../../src/approval.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\";\nimport { isAccessTokenClaims } from \"../claims.js\";\nimport { getMockAccessTokenClaims } from \"../mock.js\";\nimport { warnAuth, errorCode } from \"../log.js\";\n\nconst jwks = createRemoteJWKSet(new URL(JWKS_URL));\n\n/**\n * 검증과 진단을 한 곳에 모은다.\n *\n * 예전에는 아래 두 export 가 같은 try/catch 를 각자 갖고 있었다. 그러면\n * 로깅을 붙일 때도 벌마다 갈리고, 한쪽만 고치는 일이 생긴다.\n */\nasync function verify(token: string): Promise<AccessTokenClaims | null> {\n try {\n const { payload } = await jwtVerify(token, jwks, {\n issuer: AUTH_SERVER_URL,\n // 발급자(자택 k3s)와 검증자(Vercel)의 시계가 몇 초 어긋나면, 방금\n // 발급된 토큰이 nbf 미래로 거부되어 로그인 직후 로그아웃으로 보인다.\n clockTolerance: 10,\n });\n if (isAccessTokenClaims(payload)) return payload;\n warnAuth(\"access token claims shape mismatch\");\n return null;\n } catch (error) {\n // 정상 만료는 갱신으로 이어지는 흔한 경로라 남기지 않는다. 나머지는\n // 원인이 전혀 다르다 — 특히 ERR_JWKS_NO_MATCHING_KEY 는 키 로테이션이나\n // JWKS 도달 실패이고, 이걸 위조 토큰과 뭉개면 전 사용자가 조용히\n // 로그아웃된 이유를 알 수 없게 된다.\n const code = errorCode(error);\n if (code !== \"ERR_JWT_EXPIRED\") {\n warnAuth(\"access token verification failed\", { code });\n }\n return null;\n }\n}\n\nexport async function verifyAccessToken(): Promise<AccessTokenClaims | null> {\n const mockClaims = getMockAccessTokenClaims();\n if (mockClaims) return mockClaims;\n\n const cookieStore = await cookies();\n const token = cookieStore.get(ACCESS_TOKEN_COOKIE)?.value;\n if (!token) return null;\n\n return verify(token);\n}\n\nexport async function verifyAccessTokenString(\n token: string\n): Promise<AccessTokenClaims | null> {\n const mockClaims = getMockAccessTokenClaims();\n if (mockClaims) return mockClaims;\n\n return verify(token);\n}\n","import type { JWTPayload } from \"jose\";\nimport type { AccessTokenClaims } from \"./types.js\";\n\nexport function isAccessTokenClaims(\n payload: JWTPayload\n): payload is AccessTokenClaims {\n return (\n payload.token_type === \"access\" &&\n typeof payload.sub === \"string\" &&\n typeof payload.nickname === \"string\" &&\n typeof payload.profile_image_url === \"string\" &&\n (payload.role === \"user\" || payload.role === \"admin\") &&\n Array.isArray(payload.approved_clients) &&\n payload.approved_clients.every((clientId) => typeof clientId === \"string\")\n );\n}\n","/**\n * 진단 로그.\n *\n * 이 라이브러리는 오랫동안 실패를 전부 `null` 하나로만 알렸다. 2026-07-19 에\n * auth 서버 앞의 Cloudflare Bot Fight Mode 가 프록시의 갱신 요청을 403\n * (`cf-mitigated: challenge`) 으로 막았을 때, 남은 흔적이 하나도 없어서\n * 원인을 찾는 데 반나절이 걸렸다. 앱 쪽에 임시 진단 코드를 심고 프로덕션에\n * 두 번 배포한 뒤에야 응답 코드를 볼 수 있었다.\n *\n * 그래서 실패에는 최소한의 사실을 남긴다.\n *\n * **토큰 값은 절대 남기지 않는다.** JWT 는 base64url 이라 \"접두 8자\" 같은\n * 안전해 보이는 일부만으로도 헤더와 payload 앞부분이 그대로 복원된다.\n * `sub`, `nickname`, `approved_clients` 가 거기 들어 있다. 남길 수 있는 것은\n * 쿠키 **이름**, HTTP 상태 코드, 에러 코드처럼 값이 아닌 사실뿐이다.\n */\n\nconst PREFIX = \"[lemon-auth]\";\n\nexport function warnAuth(\n event: string,\n detail?: Record<string, unknown>\n): void {\n if (detail === undefined) {\n console.warn(`${PREFIX} ${event}`);\n return;\n }\n console.warn(`${PREFIX} ${event}`, JSON.stringify(detail));\n}\n\n/**\n * 에러에서 값 노출 없이 식별자만 뽑는다.\n *\n * jose 는 `ERR_JWKS_NO_MATCHING_KEY`(키 로테이션), `ERR_JWT_EXPIRED`(정상 만료),\n * `ERR_JWS_SIGNATURE_VERIFICATION_FAILED`(위조 또는 설정 오류) 처럼 원인이\n * 전혀 다른 실패를 구분 가능한 `code` 로 준다. 이걸 뭉개면 JWKS 장애와\n * 위조 토큰이 같은 증상으로 보인다.\n *\n * undici 의 네트워크 실패는 `cause.code` 에 `ENOTFOUND` 같은 값이 들어간다.\n */\nexport function errorCode(error: unknown): string {\n if (typeof error === \"object\" && error !== null) {\n const { code, cause } = error as { code?: unknown; cause?: unknown };\n if (typeof code === \"string\") return code;\n if (typeof cause === \"object\" && cause !== null) {\n const causeCode = (cause as { code?: unknown }).code;\n if (typeof causeCode === \"string\") return causeCode;\n }\n }\n if (error instanceof Error) return error.name;\n return \"unknown\";\n}\n","import { cache } from \"react\";\nimport { redirect } from \"next/navigation\";\nimport type { LemonSession, LemonUser } from \"../types.js\";\nimport { AUTH_SERVER_URL } from \"../constants.js\";\nimport { isApprovedClient } from \"../approval.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 interface GetSessionOptions {\n clientId?: string;\n}\n\nconst getSessionByClientId = cache(\n async (clientId?: string): Promise<LemonSession> => {\n const user = await getUser();\n if (!user) return { type: \"none\" };\n if (!isApprovedClient(user.approvedClients, clientId)) {\n return { type: \"unapproved\", user };\n }\n return { type: \"authenticated\", user };\n }\n);\n\nexport async function getSession(\n options: GetSessionOptions = {}\n): Promise<LemonSession> {\n return getSessionByClientId(options.clientId);\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 interface RequireClientOptions {\n loginRedirectTo?: string;\n unapprovedRedirectTo?: string;\n}\n\nexport async function requireClient(\n clientId: string,\n optionsOrRedirectTo: string | RequireClientOptions = \"/\"\n): Promise<LemonUser> {\n const options =\n typeof optionsOrRedirectTo === \"string\"\n ? { loginRedirectTo: optionsOrRedirectTo }\n : optionsOrRedirectTo;\n const session = await getSession({ clientId });\n if (session.type === \"none\") {\n redirect(options.loginRedirectTo ?? \"/\");\n }\n if (session.type === \"unapproved\") {\n redirect(\n options.unapprovedRedirectTo ??\n `${AUTH_SERVER_URL}/error?code=FORBIDDEN`\n );\n }\n return session.user;\n}\n","import { warnAuth } from \"./log.js\";\n\n/** fail-open 경고를 요청마다 찍지 않기 위한 플래그. */\nlet warnedMissingClientId = false;\n\nexport function isApprovedClient(\n approvedClients: string[],\n clientId?: string\n): boolean {\n if (!clientId) {\n // clientId 를 안 넘기면 승인 검사가 통째로 열린다(fail-open). 이 라이브러리를\n // clientId 없이 쓰는 앱도 있으므로 동작은 유지하지만, 환경변수가 배포에서\n // 빠져서 이렇게 된 경우와 구분이 안 된다. 후자는 인증이 사실상 없는 상태라\n // 조용히 넘어가면 안 된다.\n if (!warnedMissingClientId) {\n warnedMissingClientId = true;\n warnAuth(\n \"clientId not provided — approval check is disabled (all users pass). \" +\n \"설정 누락이 아닌지 확인할 것.\"\n );\n }\n return true;\n }\n return approvedClients.includes(clientId) || approvedClients.includes(\"*\");\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lemondouble/lemon-auth",
3
- "version": "0.9.0",
3
+ "version": "0.9.1",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/LemonDouble/lemon-auth"