diff --git a/apps/cs/package.json b/apps/cs/package.json index 03ec06fcf..832581735 100644 --- a/apps/cs/package.json +++ b/apps/cs/package.json @@ -9,6 +9,7 @@ "lint": "next lint" }, "dependencies": { + "@common/auth": "workspace:*", "@common/assets": "workspace:*", "@common/components": "workspace:*", "@common/config": "workspace:*", @@ -60,4 +61,4 @@ "tailwindcss": "^3.4.14", "typescript": "^5.7.2" } -} \ No newline at end of file +} diff --git a/apps/cs/src/entities/auth/api/login-service.ts b/apps/cs/src/entities/auth/api/login-service.ts index 6922efbbe..2181de239 100644 --- a/apps/cs/src/entities/auth/api/login-service.ts +++ b/apps/cs/src/entities/auth/api/login-service.ts @@ -1,3 +1,4 @@ +import { getRemainingMs } from "@common/auth"; import { ENDPOINT, CLAIM_NAME, @@ -10,7 +11,6 @@ import { clearAuthToken, setupRefreshTimer, } from "../lib/token-manager"; -import { getRemainingMs } from "../lib/jwt-utils"; function resolveLoginError(message: string | undefined): string { switch (message) { diff --git a/apps/cs/src/entities/auth/lib/auth-wrapper.tsx b/apps/cs/src/entities/auth/lib/auth-wrapper.tsx index ba6627d8a..c4cede0d9 100644 --- a/apps/cs/src/entities/auth/lib/auth-wrapper.tsx +++ b/apps/cs/src/entities/auth/lib/auth-wrapper.tsx @@ -3,10 +3,11 @@ import { useEffect } from "react"; import { useRouter } from "next/navigation"; import { isAxiosError } from "axios"; +import { getRemainingMs } from "@common/auth"; +import { isAtLoginRedirectUrl } from "@common/network"; import { client } from "@/shared/api"; import type { IUserInfo } from "@/shared/store"; import { useUserStore } from "@/shared/lib"; -import { isAtLoginRedirectUrl } from "@common/network"; import { ENDPOINT, AUTH_USER_ID, @@ -14,7 +15,6 @@ import { CLAIM_NAME, } from "@/shared/config"; import { getToken, setupRefreshTimer } from "./token-manager"; -import { getRemainingMs } from "./jwt-utils"; import { silentRefresh } from "../api/login-service"; const AuthWrapper = ({ children }: { children: React.ReactNode }) => { diff --git a/apps/cs/src/entities/auth/lib/token-manager.ts b/apps/cs/src/entities/auth/lib/token-manager.ts index bef544141..44aa888aa 100644 --- a/apps/cs/src/entities/auth/lib/token-manager.ts +++ b/apps/cs/src/entities/auth/lib/token-manager.ts @@ -1,62 +1,15 @@ -import { AUTH_USER_ID, CLAIM_NAME, COOKIE_DOMAIN } from "@/shared/config"; -import { getCookie, setCookie, removeCookie } from "@/shared/lib"; +import { createTokenManager } from "@common/auth"; +import { authConfig } from "@/shared/config/auth"; -let refreshTimer: ReturnType | null = null; +export const tokenManager = createTokenManager(authConfig); -export function getToken(key: string): string | undefined { - return getCookie(key); -} - -export function setToken(key: string, value: string): void { - const domain = getCookieDomain(); - const isHttps = - typeof window !== "undefined" && window.location.protocol === "https:"; - - setCookie(key, value, { - secure: isHttps, - sameSite: "lax", - domain, - path: "/", - }); -} - -export function removeToken(key: string): void { - const domain = getCookieDomain(); - removeCookie(key, { domain, path: "/" }); -} - -export function setupRefreshTimer( - callback: () => Promise, - delayMs: number, -): void { - if (refreshTimer) { - clearTimeout(refreshTimer); - } - - refreshTimer = setTimeout(() => { - callback().catch((error) => { - console.error("Silent refresh timer callback failed:", error); - }); - }, delayMs); -} - -export function clearAuthToken(): void { - if (refreshTimer) { - clearTimeout(refreshTimer); - refreshTimer = null; - } - - removeToken(CLAIM_NAME); - removeToken(AUTH_USER_ID); -} - -function getCookieDomain(): string | undefined { - if (typeof window === "undefined") return undefined; - - if (COOKIE_DOMAIN) return COOKIE_DOMAIN; - - const { hostname } = window.location; - if (hostname === "localhost" || hostname === "127.0.0.1") return undefined; - - return hostname; -} +export const { + getToken, + setToken, + removeToken, + setupRefreshTimer, + clearAuthToken, + getAccessToken, + getRefreshToken, + getUserId, +} = tokenManager; diff --git a/apps/cs/src/shared/config/auth.ts b/apps/cs/src/shared/config/auth.ts new file mode 100644 index 000000000..d903525af --- /dev/null +++ b/apps/cs/src/shared/config/auth.ts @@ -0,0 +1,16 @@ +import type { AuthConfig } from "@common/auth"; +import { + ACCESS_TOKEN, + AUTH_USER_ID, + CLAIM_NAME, + COOKIE_DOMAIN, + REFRESH_TOKEN, +} from "./env"; + +export const authConfig: AuthConfig = { + authUserId: AUTH_USER_ID || "token-id", + claimName: CLAIM_NAME || "Authorization", + cookieDomain: COOKIE_DOMAIN, + accessToken: ACCESS_TOKEN || "access-token", + refreshToken: REFRESH_TOKEN || "refresh-token", +}; diff --git a/packages/common/auth/README.md b/packages/common/auth/README.md new file mode 100644 index 000000000..5cd54d787 --- /dev/null +++ b/packages/common/auth/README.md @@ -0,0 +1,492 @@ +# @common/auth + +인증 관련 공통 기능을 제공하는 독립 패키지입니다. 의존성 주입 패턴을 통해 각 앱의 환경변수를 주입받아 재사용 가능한 토큰 관리 기능을 제공합니다. + +## 특징 + +- **의존성 주입**: 환경변수를 직접 참조하지 않고 설정 객체를 주입받음 +- **재사용성**: 모든 앱에서 동일한 로직 사용 가능 +- **타입 안정성**: TypeScript 인터페이스로 계약 보장 +- **테스트 가능성**: Mock 설정 주입으로 단위 테스트 용이 + +## 패키지 구조 + +``` +packages/common/auth/ +├── package.json # 패키지 정의 +├── tsconfig.json # TypeScript 설정 +├── index.ts # 진입점 (Public API) +├── types.ts # 타입 정의 +├── token-manager.ts # 토큰 관리 로직 +├── cookie.ts # 쿠키 유틸리티 +├── jwt-utils.ts # JWT 유틸리티 +└── README.md # 문서 +``` + +## 설치 + +각 앱의 `package.json`에 의존성을 추가합니다: + +```json +{ + "dependencies": { + "@common/auth": "workspace:*" + } +} +``` + +## 사용 방법 + +### 1. 설정 파일 생성 + +각 앱에서 자신의 환경변수로 `AuthConfig` 객체를 생성합니다. + +```typescript +// apps/cs/src/shared/config/auth.ts +import type { AuthConfig } from "@common/auth/types"; + +export const authConfig: AuthConfig = { + authUserId: process.env.NEXT_PUBLIC_AUTH_USER_ID || "token-id", + claimName: process.env.NEXT_PUBLIC_CLAIM_NAME || "Authorization", + cookieDomain: process.env.NEXT_PUBLIC_COOKIE_DOMAIN, + accessToken: process.env.NEXT_PUBLIC_ACCESS_TOKEN || "access-token", + refreshToken: process.env.NEXT_PUBLIC_REFRESH_TOKEN || "refresh-token", +}; +``` + +### 2. TokenManager 인스턴스 생성 + +설정을 주입하여 토큰 매니저 인스턴스를 생성합니다. + +```typescript +// apps/cs/src/entities/auth/lib/token-manager.ts +import { createTokenManager } from "@common/auth"; +import { authConfig } from "@/shared/config/auth"; + +export const tokenManager = createTokenManager(authConfig); + +// 편의를 위한 re-export +export const { + getToken, + setToken, + removeToken, + setupRefreshTimer, + clearAuthToken, + getAccessToken, + getRefreshToken, + getUserId, +} = tokenManager; +``` + +### 3. 컴포넌트에서 사용 + +```typescript +// apps/cs/src/features/auth/ui/LoginForm.tsx +import { tokenManager } from "@/entities/auth/lib/token-manager"; +import { authConfig } from "@/shared/config/auth"; + +export function LoginForm() { + const handleLogin = async (credentials: LoginCredentials) => { + const response = await loginApi(credentials); + + // 토큰 저장 + tokenManager.setToken(authConfig.claimName, response.accessToken); + tokenManager.setToken(authConfig.refreshToken, response.refreshToken); + + // 또는 편의 메서드 사용 + // tokenManager.getAccessToken(); + // tokenManager.getRefreshToken(); + // tokenManager.getUserId(); + }; + + return ( + // ... JSX + ); +} +``` + +## API 문서 + +### `createTokenManager(config: AuthConfig): TokenManager` + +토큰 매니저 인스턴스를 생성하는 팩토리 함수입니다. + +**Parameters:** +- `config: AuthConfig` - 인증 설정 객체 + +**Returns:** +- `TokenManager` - 토큰 관리 메서드를 포함한 객체 + +### TokenManager 메서드 + +#### `getToken(key: string): string | undefined` + +지정된 키의 쿠키 값을 가져옵니다. + +```typescript +const token = tokenManager.getToken("access-token"); +``` + +#### `setToken(key: string, value: string): void` + +토큰을 쿠키에 저장합니다. 자동으로 도메인, secure, sameSite 설정을 적용합니다. + +```typescript +tokenManager.setToken("access-token", "eyJhbGc..."); +``` + +#### `removeToken(key: string): void` + +지정된 키의 쿠키를 삭제합니다. + +```typescript +tokenManager.removeToken("access-token"); +``` + +#### `setupRefreshTimer(callback: () => Promise, delayMs: number): void` + +토큰 갱신 타이머를 설정합니다. + +```typescript +tokenManager.setupRefreshTimer(async () => { + await refreshAccessToken(); +}, 3600000); // 1시간 후 +``` + +#### `clearAuthToken(): void` + +모든 인증 토큰을 삭제하고 타이머를 정리합니다. + +```typescript +tokenManager.clearAuthToken(); +``` + +#### `getAccessToken(): string | undefined` + +액세스 토큰을 가져오는 편의 메서드입니다. + +```typescript +const accessToken = tokenManager.getAccessToken(); +``` + +#### `getRefreshToken(): string | undefined` + +리프레시 토큰을 가져오는 편의 메서드입니다. + +```typescript +const refreshToken = tokenManager.getRefreshToken(); +``` + +#### `getUserId(): string | undefined` + +사용자 ID 토큰을 가져오는 편의 메서드입니다. + +```typescript +const userId = tokenManager.getUserId(); +``` + +### JWT 유틸리티 + +#### `getJwtExpiry(token: string): number | null` + +JWT 토큰의 만료 시간(Unix timestamp, 초 단위)을 반환합니다. + +```typescript +import { getJwtExpiry } from "@common/auth"; + +const expiry = getJwtExpiry(token); +if (expiry) { + console.log("Token expires at:", new Date(expiry * 1000)); +} +``` + +#### `getRemainingMs(token: string): number` + +토큰 만료 1분 전까지 남은 시간(밀리초)을 반환합니다. + +```typescript +import { getRemainingMs } from "@common/auth"; + +const remaining = getRemainingMs(token); +if (remaining > 0) { + console.log("Token valid for:", remaining, "ms"); +} +``` + +### 쿠키 유틸리티 + +#### `getCookie(name: string): string | undefined` + +쿠키 값을 가져옵니다. + +```typescript +import { getCookie } from "@common/auth"; + +const value = getCookie("my-cookie"); +``` + +#### `setCookie(name: string, value: string, options?: CookieOptions): void` + +쿠키를 설정합니다. + +```typescript +import { setCookie } from "@common/auth"; + +setCookie("my-cookie", "value", { + secure: true, + sameSite: "lax", + path: "/", +}); +``` + +#### `removeCookie(name: string, options?: CookieOptions): void` + +쿠키를 삭제합니다. + +```typescript +import { removeCookie } from "@common/auth"; + +removeCookie("my-cookie", { path: "/" }); +``` + +## 타입 정의 + +### `AuthConfig` + +```typescript +interface AuthConfig { + authUserId: string; // 사용자 ID 토큰 키 + claimName: string; // 액세스 토큰 키 + cookieDomain?: string; // 쿠키 도메인 (선택) + accessToken?: string; // 액세스 토큰 키 (선택) + refreshToken?: string; // 리프레시 토큰 키 (선택) +} +``` + +### `CookieOptions` + +```typescript +interface CookieOptions { + secure?: boolean; + sameSite?: "strict" | "lax" | "none"; + domain?: string; + path?: string; + expires?: number | Date; +} +``` + +## 마이그레이션 가이드 + +### 기존 앱에서 @common/auth로 마이그레이션 + +#### 1. 의존성 추가 + +```bash +# 앱 디렉토리에서 +cd apps/your-app +``` + +`package.json`에 추가: +```json +{ + "dependencies": { + "@common/auth": "workspace:*" + } +} +``` + +```bash +pnpm install +``` + +#### 2. 설정 파일 생성 + +```typescript +// apps/your-app/src/shared/config/auth.ts +import type { AuthConfig } from "@common/auth/types"; + +export const authConfig: AuthConfig = { + authUserId: process.env.NEXT_PUBLIC_AUTH_USER_ID || "token-id", + claimName: process.env.NEXT_PUBLIC_CLAIM_NAME || "Authorization", + cookieDomain: process.env.NEXT_PUBLIC_COOKIE_DOMAIN, + accessToken: process.env.NEXT_PUBLIC_ACCESS_TOKEN || "access-token", + refreshToken: process.env.NEXT_PUBLIC_REFRESH_TOKEN || "refresh-token", +}; +``` + +#### 3. TokenManager 수정 + +**Before (기존 코드):** +```typescript +// apps/your-app/src/entities/auth/lib/token-manager.ts +import { AUTH_USER_ID, CLAIM_NAME } from "@/shared/config"; +import { getCookie, setCookie, removeCookie } from "@/shared/lib"; + +export function getToken(key: string): string | undefined { + return getCookie(key); +} + +export function clearAuthToken(): void { + removeToken(CLAIM_NAME); + removeToken(AUTH_USER_ID); +} +``` + +**After (@common/auth 사용):** +```typescript +// apps/your-app/src/entities/auth/lib/token-manager.ts +import { createTokenManager } from "@common/auth"; +import { authConfig } from "@/shared/config/auth"; + +export const tokenManager = createTokenManager(authConfig); + +// 기존 코드와의 호환성을 위한 re-export +export const { + getToken, + setToken, + removeToken, + setupRefreshTimer, + clearAuthToken, + getAccessToken, + getRefreshToken, + getUserId, +} = tokenManager; +``` + +#### 4. 사용처 업데이트 + +**클래스 기반 (st, cm 앱):** +```typescript +// Before +import { TokenManager } from "@/entities/auth/lib/token-manager"; +const tokenManager = TokenManager.getInstance(); + +// After +import { tokenManager } from "@/entities/auth/lib/token-manager"; +// 그대로 사용 가능 +``` + +**함수형 (cs 앱):** +```typescript +// Before +import { getToken, setToken, clearAuthToken } from "@/entities/auth/lib/token-manager"; + +// After +import { getToken, setToken, clearAuthToken } from "@/entities/auth/lib/token-manager"; +// 그대로 사용 가능 (re-export 덕분) +``` + +## 다른 앱에서 사용 예시 + +### AS 앱 + +```typescript +// apps/as/src/shared/config/auth.ts +import type { AuthConfig } from "@common/auth/types"; + +export const authConfig: AuthConfig = { + authUserId: process.env.NEXT_PUBLIC_AS_AUTH_USER_ID || "as-token-id", + claimName: process.env.NEXT_PUBLIC_AS_CLAIM_NAME || "X-AS-Auth", + cookieDomain: process.env.NEXT_PUBLIC_AS_COOKIE_DOMAIN, + accessToken: process.env.NEXT_PUBLIC_AS_ACCESS_TOKEN || "as-access-token", + refreshToken: process.env.NEXT_PUBLIC_AS_REFRESH_TOKEN || "as-refresh-token", +}; +``` + +### ST 앱 + +```typescript +// apps/st/src/shared/config/auth.ts +import type { AuthConfig } from "@common/auth/types"; + +export const authConfig: AuthConfig = { + authUserId: process.env.NEXT_PUBLIC_AUTH_USER_ID || "token-id", + claimName: process.env.NEXT_PUBLIC_CLAIM_NAME || "Authorization", + cookieDomain: ".hasuon.or.kr", // 하드코딩된 도메인도 설정으로 이동 + accessToken: process.env.NEXT_PUBLIC_ACCESS_TOKEN || "access-token", + refreshToken: process.env.NEXT_PUBLIC_REFRESH_TOKEN || "refresh-token", +}; +``` + +## 환경변수 설정 + +각 앱의 `.env.local` 파일: + +```bash +# CS 앱 +NEXT_PUBLIC_CLAIM_NAME=Authorization +NEXT_PUBLIC_ACCESS_TOKEN=access-token +NEXT_PUBLIC_REFRESH_TOKEN=refresh-token +NEXT_PUBLIC_AUTH_USER_ID=token-id +NEXT_PUBLIC_COOKIE_DOMAIN=.example.com + +# AS 앱 (다른 설정 사용 가능) +NEXT_PUBLIC_AS_CLAIM_NAME=X-AS-Auth +NEXT_PUBLIC_AS_ACCESS_TOKEN=as-access-token +NEXT_PUBLIC_AS_REFRESH_TOKEN=as-refresh-token +NEXT_PUBLIC_AS_AUTH_USER_ID=as-token-id +NEXT_PUBLIC_AS_COOKIE_DOMAIN=.as.example.com +``` + +## 테스트 예시 + +```typescript +import { createTokenManager } from "@common/auth"; +import type { AuthConfig } from "@common/auth/types"; + +describe("TokenManager", () => { + const mockConfig: AuthConfig = { + authUserId: "test-user-id", + claimName: "test-claim", + cookieDomain: ".test.com", + accessToken: "test-access", + refreshToken: "test-refresh", + }; + + it("should create token manager with config", () => { + const tokenManager = createTokenManager(mockConfig); + expect(tokenManager).toBeDefined(); + expect(tokenManager.getToken).toBeDefined(); + }); + + it("should use injected config", () => { + const tokenManager = createTokenManager(mockConfig); + // 테스트 로직... + }); +}); +``` + +## 주의사항 + +1. **환경변수는 빌드 타임에 주입됩니다**: `process.env.NEXT_PUBLIC_*` 값은 빌드 시점에 정적으로 치환됩니다. + +2. **쿠키 도메인 자동 감지**: `cookieDomain`을 설정하지 않으면 자동으로 현재 호스트명을 사용합니다. + - `localhost` / `127.0.0.1`: 도메인 설정 안 함 + - 기타: 현재 호스트명 사용 + +3. **HTTPS 자동 감지**: `secure` 옵션은 현재 프로토콜에 따라 자동 설정됩니다. + +4. **타이머 관리**: `setupRefreshTimer`는 이전 타이머를 자동으로 정리합니다. + +## 문제 해결 + +### TypeScript 오류: "Cannot find module '@common/auth'" + +1. `pnpm install` 실행 +2. TypeScript 서버 재시작 +3. `tsconfig.json`의 `paths` 설정 확인 + +### 쿠키가 저장되지 않음 + +1. 브라우저 개발자 도구에서 쿠키 확인 +2. `cookieDomain` 설정 확인 +3. HTTPS 환경에서 `secure: true` 확인 + +### 토큰이 다른 앱에서 공유되지 않음 + +1. `cookieDomain` 설정이 공통 도메인(예: `.example.com`)인지 확인 +2. 각 앱의 토큰 키 이름이 다른지 확인 (충돌 방지) + +## 라이선스 + +ISC diff --git a/packages/common/auth/cookie.ts b/packages/common/auth/cookie.ts new file mode 100644 index 000000000..8f5d0011b --- /dev/null +++ b/packages/common/auth/cookie.ts @@ -0,0 +1,29 @@ +import Cookies from "js-cookie"; + +export function getCookie(name: string): string | undefined { + if (typeof window === "undefined") { + return undefined; + } + return Cookies.get(name); +} + +export function setCookie( + name: string, + value: string, + options?: Cookies.CookieAttributes, +): void { + if (typeof window === "undefined") { + return; + } + Cookies.set(name, value, options); +} + +export function removeCookie( + name: string, + options?: Cookies.CookieAttributes, +): void { + if (typeof window === "undefined") { + return; + } + Cookies.remove(name, options); +} diff --git a/packages/common/auth/index.ts b/packages/common/auth/index.ts new file mode 100644 index 000000000..04fb01bb4 --- /dev/null +++ b/packages/common/auth/index.ts @@ -0,0 +1,7 @@ +export { createTokenManager } from "./token-manager"; +export type { TokenManager } from "./token-manager"; + +export { getCookie, setCookie, removeCookie } from "./cookie"; +export { getJwtExpiry, getRemainingMs } from "./jwt-utils"; + +export type { AuthConfig, CookieOptions } from "./types"; diff --git a/apps/cs/src/entities/auth/lib/jwt-utils.ts b/packages/common/auth/jwt-utils.ts similarity index 100% rename from apps/cs/src/entities/auth/lib/jwt-utils.ts rename to packages/common/auth/jwt-utils.ts diff --git a/packages/common/auth/package.json b/packages/common/auth/package.json new file mode 100644 index 000000000..4baa26d35 --- /dev/null +++ b/packages/common/auth/package.json @@ -0,0 +1,18 @@ +{ + "name": "@common/auth", + "version": "1.0.0", + "private": true, + "main": "./index.ts", + "types": "./index.ts", + "exports": { + ".": "./index.ts" + }, + "dependencies": { + "js-cookie": "^3.0.5" + }, + "devDependencies": { + "@types/js-cookie": "^3.0.6", + "@types/node": "^22.13.1", + "typescript": "^5.7.2" + } +} diff --git a/packages/common/auth/token-manager.ts b/packages/common/auth/token-manager.ts new file mode 100644 index 000000000..ff516aa16 --- /dev/null +++ b/packages/common/auth/token-manager.ts @@ -0,0 +1,77 @@ +import type { AuthConfig } from "./types"; +import { getCookie, setCookie, removeCookie } from "./cookie"; + +export function createTokenManager(config: AuthConfig) { + let refreshTimer: ReturnType | null = null; + + const getCookieDomain = (): string | undefined => { + if (typeof window === "undefined") return undefined; + + if (config.cookieDomain) return config.cookieDomain; + + const { hostname } = window.location; + if (hostname === "localhost" || hostname === "127.0.0.1") return undefined; + + return hostname; + }; + + return { + getToken(key: string): string | undefined { + return getCookie(key); + }, + + setToken(key: string, value: string): void { + const domain = getCookieDomain(); + const isHttps = + typeof window !== "undefined" && window.location.protocol === "https:"; + + setCookie(key, value, { + secure: isHttps, + sameSite: "lax", + domain, + path: "/", + }); + }, + + removeToken(key: string): void { + const domain = getCookieDomain(); + removeCookie(key, { domain, path: "/" }); + }, + + setupRefreshTimer(callback: () => Promise, delayMs: number): void { + if (refreshTimer) { + clearTimeout(refreshTimer); + } + + refreshTimer = setTimeout(() => { + callback().catch((error) => { + console.error("Silent refresh timer callback failed:", error); + }); + }, delayMs); + }, + + clearAuthToken(): void { + if (refreshTimer) { + clearTimeout(refreshTimer); + refreshTimer = null; + } + + this.removeToken(config.claimName); + this.removeToken(config.authUserId); + }, + + getAccessToken(): string | undefined { + return this.getToken(config.claimName); + }, + + getRefreshToken(): string | undefined { + return config.refreshToken ? this.getToken(config.refreshToken) : undefined; + }, + + getUserId(): string | undefined { + return this.getToken(config.authUserId); + }, + }; +} + +export type TokenManager = ReturnType; diff --git a/packages/common/auth/tsconfig.json b/packages/common/auth/tsconfig.json new file mode 100644 index 000000000..04a0208c5 --- /dev/null +++ b/packages/common/auth/tsconfig.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "target": "ES2017", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "verbatimModuleSyntax": true, + "strictNullChecks": true, + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true + }, + "include": ["**/*.ts", "**/*.tsx"], + "exclude": ["node_modules"] +} diff --git a/packages/common/auth/types.ts b/packages/common/auth/types.ts new file mode 100644 index 000000000..077eb821f --- /dev/null +++ b/packages/common/auth/types.ts @@ -0,0 +1,15 @@ +export interface AuthConfig { + authUserId: string; + claimName: string; + cookieDomain?: string; + accessToken?: string; + refreshToken?: string; +} + +export interface CookieOptions { + secure?: boolean; + sameSite?: "strict" | "lax" | "none"; + domain?: string; + path?: string; + expires?: number | Date; +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e39d4b41f..2b497a297 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -101,10 +101,10 @@ importers: devDependencies: eslint: specifier: ^9.0.0 - version: 9.39.2(jiti@2.6.1) + version: 9.39.2(jiti@1.21.7) eslint-config-next: specifier: 15.4.0 - version: 15.4.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + version: 15.4.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) postcss: specifier: ^8.4.49 version: 8.5.6 @@ -407,6 +407,9 @@ importers: '@common/assets': specifier: workspace:* version: link:../../packages/common/assets + '@common/auth': + specifier: workspace:* + version: link:../../packages/common/auth '@common/components': specifier: workspace:* version: link:../../packages/common/components @@ -1190,13 +1193,13 @@ importers: version: 5.28.5 eslint: specifier: ^9 - version: 9.39.2(jiti@1.21.7) + version: 9.39.2(jiti@2.6.1) eslint-config-next: specifier: 15.1.7 - version: 15.1.7(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + version: 15.1.7(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) eslint-plugin-react-hooks: specifier: ^5.0.0 - version: 5.2.0(eslint@9.39.2(jiti@1.21.7)) + version: 5.2.0(eslint@9.39.2(jiti@2.6.1)) postcss: specifier: ^8 version: 8.5.6 @@ -1358,6 +1361,22 @@ importers: specifier: ^5.3.3 version: 5.9.3 + packages/common/auth: + dependencies: + js-cookie: + specifier: ^3.0.5 + version: 3.0.5 + devDependencies: + '@types/js-cookie': + specifier: ^3.0.6 + version: 3.0.6 + '@types/node': + specifier: ^22.13.1 + version: 22.19.2 + typescript: + specifier: ^5.7.2 + version: 5.9.3 + packages/common/business_components: dependencies: '@common/components': @@ -10729,26 +10748,6 @@ snapshots: optionalDependencies: source-map: 0.7.6 - eslint-config-next@15.1.7(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3): - dependencies: - '@next/eslint-plugin-next': 15.1.7 - '@rushstack/eslint-patch': 1.15.0 - '@typescript-eslint/eslint-plugin': 8.49.0(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) - '@typescript-eslint/parser': 8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) - eslint: 9.39.2(jiti@1.21.7) - eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.2(jiti@1.21.7)) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@1.21.7)) - eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.2(jiti@1.21.7)) - eslint-plugin-react: 7.37.5(eslint@9.39.2(jiti@1.21.7)) - eslint-plugin-react-hooks: 5.2.0(eslint@9.39.2(jiti@1.21.7)) - optionalDependencies: - typescript: 5.9.3 - transitivePeerDependencies: - - eslint-import-resolver-webpack - - eslint-plugin-import-x - - supports-color - eslint-config-next@15.1.7(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3): dependencies: '@next/eslint-plugin-next': 15.1.7 @@ -10757,8 +10756,8 @@ snapshots: '@typescript-eslint/parser': 8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) eslint: 9.39.2(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.2(jiti@2.6.1)) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@2.6.1)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.2(jiti@2.6.1)) eslint-plugin-react: 7.37.5(eslint@9.39.2(jiti@2.6.1)) eslint-plugin-react-hooks: 5.2.0(eslint@9.39.2(jiti@2.6.1)) @@ -10769,6 +10768,26 @@ snapshots: - eslint-plugin-import-x - supports-color + eslint-config-next@15.4.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3): + dependencies: + '@next/eslint-plugin-next': 15.4.0 + '@rushstack/eslint-patch': 1.15.0 + '@typescript-eslint/eslint-plugin': 8.49.0(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/parser': 8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + eslint: 9.39.2(jiti@1.21.7) + eslint-import-resolver-node: 0.3.9 + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.2(jiti@1.21.7)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.2(jiti@1.21.7)) + eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.2(jiti@1.21.7)) + eslint-plugin-react: 7.37.5(eslint@9.39.2(jiti@1.21.7)) + eslint-plugin-react-hooks: 5.2.0(eslint@9.39.2(jiti@1.21.7)) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - eslint-import-resolver-webpack + - eslint-plugin-import-x + - supports-color + eslint-config-next@15.4.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3): dependencies: '@next/eslint-plugin-next': 15.4.0 @@ -10797,6 +10816,21 @@ snapshots: transitivePeerDependencies: - supports-color + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)): + dependencies: + '@nolyfill/is-core-module': 1.0.39 + debug: 4.4.3 + eslint: 9.39.2(jiti@2.6.1) + get-tsconfig: 4.13.0 + is-bun-module: 2.0.0 + stable-hash: 0.0.5 + tinyglobby: 0.2.15 + unrs-resolver: 1.11.1 + optionalDependencies: + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)) + transitivePeerDependencies: + - supports-color + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.2(jiti@1.21.7)): dependencies: '@nolyfill/is-core-module': 1.0.39 @@ -10808,7 +10842,7 @@ snapshots: tinyglobby: 0.2.15 unrs-resolver: 1.11.1 optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@1.21.7)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.2(jiti@1.21.7)) transitivePeerDependencies: - supports-color @@ -10838,6 +10872,17 @@ snapshots: transitivePeerDependencies: - supports-color + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)): + dependencies: + debug: 3.2.7 + optionalDependencies: + '@typescript-eslint/parser': 8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + eslint: 9.39.2(jiti@2.6.1) + eslint-import-resolver-node: 0.3.9 + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)) + transitivePeerDependencies: + - supports-color + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@2.6.1)): dependencies: debug: 3.2.7 @@ -10849,7 +10894,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@1.21.7)): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.2(jiti@1.21.7)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -10878,6 +10923,35 @@ snapshots: - eslint-import-resolver-webpack - supports-color + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)): + dependencies: + '@rtsao/scc': 1.1.0 + array-includes: 3.1.9 + array.prototype.findlastindex: 1.2.6 + array.prototype.flat: 1.3.3 + array.prototype.flatmap: 1.3.3 + debug: 3.2.7 + doctrine: 2.1.0 + eslint: 9.39.2(jiti@2.6.1) + eslint-import-resolver-node: 0.3.9 + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)) + hasown: 2.0.2 + is-core-module: 2.16.1 + is-glob: 4.0.3 + minimatch: 3.1.2 + object.fromentries: 2.0.8 + object.groupby: 1.0.3 + object.values: 1.2.1 + semver: 6.3.1 + string.prototype.trimend: 1.0.9 + tsconfig-paths: 3.15.0 + optionalDependencies: + '@typescript-eslint/parser': 8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + transitivePeerDependencies: + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - supports-color + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@2.6.1)): dependencies: '@rtsao/scc': 1.1.0