@thinkingcat/auth-utils 1.0.49 → 2.0.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.
@@ -0,0 +1,15 @@
1
+ import type { NextResponse, NextRequest } from 'next/server';
2
+ /**
3
+ * access token과 refresh token을 사용하여 완전한 인증 세션 생성
4
+ */
5
+ export declare function createAuthResponse(accessToken: string, secret: string, options: {
6
+ req: NextRequest;
7
+ refreshToken?: string;
8
+ redirectPath?: string;
9
+ text?: string;
10
+ cookiePrefix?: string;
11
+ isProduction?: boolean;
12
+ cookieDomain?: string;
13
+ serviceId: string;
14
+ licenseKey: string;
15
+ }): Promise<NextResponse>;
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createAuthResponse = createAuthResponse;
4
+ const license_1 = require("../utils/license");
5
+ const jwt_1 = require("./jwt");
6
+ const server_1 = require("../utils/server");
7
+ const cookies_1 = require("./cookies");
8
+ const logger_1 = require("../utils/logger");
9
+ /**
10
+ * access token과 refresh token을 사용하여 완전한 인증 세션 생성
11
+ */
12
+ async function createAuthResponse(accessToken, secret, options) {
13
+ await (0, license_1.checkLicenseKey)(options.licenseKey);
14
+ const { req, refreshToken, redirectPath, text, cookiePrefix, isProduction = false, cookieDomain, serviceId, } = options;
15
+ const tokenResult = await (0, jwt_1.verifyToken)(accessToken, secret);
16
+ if (!tokenResult) {
17
+ throw new Error('Invalid token');
18
+ }
19
+ const { payload } = tokenResult;
20
+ const jwt = (0, jwt_1.createNextAuthJWT)(payload, serviceId);
21
+ if (refreshToken) {
22
+ jwt.refreshToken = refreshToken;
23
+ }
24
+ jwt.accessTokenExpires = Date.now() + (15 * 60 * 1000);
25
+ (0, logger_1.debugLog)('createAuthResponse', 'JWT prepared', { jwtId: jwt?.id });
26
+ const { NextResponse: NextResponseClass } = await (0, server_1.getNextServer)();
27
+ const response = redirectPath
28
+ ? NextResponseClass.redirect(new URL(redirectPath, req.url), { status: 302 })
29
+ : NextResponseClass.json({ success: true, message: text || 'Authentication successful' }, { status: 200 });
30
+ if (refreshToken) {
31
+ (0, cookies_1.setCustomTokens)(response, accessToken, refreshToken, {
32
+ cookiePrefix,
33
+ isProduction,
34
+ cookieDomain,
35
+ });
36
+ }
37
+ else {
38
+ (0, cookies_1.setCustomTokens)(response, accessToken, {
39
+ cookiePrefix,
40
+ isProduction,
41
+ cookieDomain,
42
+ });
43
+ }
44
+ return response;
45
+ }
@@ -0,0 +1,33 @@
1
+ import type { ResponseLike } from '../types';
2
+ /**
3
+ * NextAuth 세션 토큰 쿠키를 삭제하는 헬퍼 함수
4
+ */
5
+ export declare function deleteNextAuthSessionCookie(response: ResponseLike, isProduction: boolean): void;
6
+ /**
7
+ * 인증 쿠키를 삭제하는 헬퍼 함수
8
+ */
9
+ export declare function clearAuthCookies(response: ResponseLike, cookiePrefix: string): void;
10
+ /**
11
+ * 모든 인증 관련 쿠키를 삭제하는 헬퍼 함수
12
+ */
13
+ export declare function clearAllAuthCookies(response: ResponseLike, cookiePrefix: string, isProduction: boolean): void;
14
+ /**
15
+ * 자체 토큰만 설정 (access token, refresh token)
16
+ */
17
+ export declare function setCustomTokens(response: ResponseLike, accessToken: string, optionsOrRefreshToken?: string | {
18
+ refreshToken?: string;
19
+ cookiePrefix?: string;
20
+ isProduction?: boolean;
21
+ cookieDomain?: string;
22
+ }, options?: {
23
+ cookiePrefix?: string;
24
+ isProduction?: boolean;
25
+ cookieDomain?: string;
26
+ }): void;
27
+ /**
28
+ * NextAuth 세션 토큰만 설정
29
+ */
30
+ export declare function setNextAuthToken(response: ResponseLike, sessionToken: string, options?: {
31
+ isProduction?: boolean;
32
+ cookieDomain?: string;
33
+ }): void;
@@ -0,0 +1,102 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.deleteNextAuthSessionCookie = deleteNextAuthSessionCookie;
4
+ exports.clearAuthCookies = clearAuthCookies;
5
+ exports.clearAllAuthCookies = clearAllAuthCookies;
6
+ exports.setCustomTokens = setCustomTokens;
7
+ exports.setNextAuthToken = setNextAuthToken;
8
+ const logger_1 = require("../utils/logger");
9
+ /**
10
+ * NextAuth 세션 토큰 쿠키를 삭제하는 헬퍼 함수
11
+ */
12
+ function deleteNextAuthSessionCookie(response, isProduction) {
13
+ const cookieName = isProduction
14
+ ? '__Secure-next-auth.session-token'
15
+ : 'next-auth.session-token';
16
+ response.cookies.delete(cookieName);
17
+ }
18
+ /**
19
+ * 인증 쿠키를 삭제하는 헬퍼 함수
20
+ */
21
+ function clearAuthCookies(response, cookiePrefix) {
22
+ response.cookies.delete(`${cookiePrefix}_access_token`);
23
+ response.cookies.delete(`${cookiePrefix}_refresh_token`);
24
+ }
25
+ /**
26
+ * 모든 인증 관련 쿠키를 삭제하는 헬퍼 함수
27
+ */
28
+ function clearAllAuthCookies(response, cookiePrefix, isProduction) {
29
+ clearAuthCookies(response, cookiePrefix);
30
+ deleteNextAuthSessionCookie(response, isProduction);
31
+ }
32
+ /**
33
+ * 자체 토큰만 설정 (access token, refresh token)
34
+ */
35
+ function setCustomTokens(response, accessToken, optionsOrRefreshToken, options) {
36
+ let refreshTokenValue;
37
+ let cookiePrefix;
38
+ let isProduction;
39
+ let cookieDomain;
40
+ if (typeof optionsOrRefreshToken === 'string') {
41
+ refreshTokenValue = optionsOrRefreshToken;
42
+ const { cookiePrefix: prefix, isProduction: prod = false, cookieDomain: domain, } = options || {};
43
+ if (!prefix) {
44
+ throw new Error('cookiePrefix is required');
45
+ }
46
+ cookiePrefix = prefix;
47
+ isProduction = prod;
48
+ cookieDomain = domain;
49
+ }
50
+ else {
51
+ const opts = optionsOrRefreshToken || {};
52
+ refreshTokenValue = opts.refreshToken;
53
+ if (!opts.cookiePrefix) {
54
+ throw new Error('cookiePrefix is required');
55
+ }
56
+ cookiePrefix = opts.cookiePrefix;
57
+ isProduction = opts.isProduction || false;
58
+ cookieDomain = opts.cookieDomain;
59
+ }
60
+ const cookieOptions = {
61
+ httpOnly: true,
62
+ secure: isProduction,
63
+ sameSite: isProduction ? 'none' : 'lax',
64
+ path: '/',
65
+ maxAge: 15 * 60,
66
+ };
67
+ if (cookieDomain) {
68
+ cookieOptions.domain = cookieDomain;
69
+ }
70
+ const accessTokenName = `${cookiePrefix}_access_token`;
71
+ response.cookies.delete(accessTokenName);
72
+ response.cookies.set(accessTokenName, accessToken, cookieOptions);
73
+ (0, logger_1.debugLog)('setCustomTokens', `Set ${accessTokenName} cookie`, { domain: cookieOptions.domain });
74
+ if (refreshTokenValue) {
75
+ const refreshTokenName = `${cookiePrefix}_refresh_token`;
76
+ const refreshCookieOptions = {
77
+ ...cookieOptions,
78
+ maxAge: 30 * 24 * 60 * 60,
79
+ };
80
+ response.cookies.set(refreshTokenName, refreshTokenValue, refreshCookieOptions);
81
+ }
82
+ }
83
+ /**
84
+ * NextAuth 세션 토큰만 설정
85
+ */
86
+ function setNextAuthToken(response, sessionToken, options = {}) {
87
+ const { isProduction = false, cookieDomain, } = options;
88
+ // Circular dependency avoidance: use dynamic require or move createNextAuthCookies to a shared file
89
+ // For now, we'll implement the logic directly to avoid complexity
90
+ const isSecure = isProduction;
91
+ const sameSiteValue = cookieDomain ? 'lax' : (isSecure ? 'none' : 'lax');
92
+ const cookieName = isSecure ? `__Secure-next-auth.session-token` : `next-auth.session-token`;
93
+ response.cookies.delete(cookieName);
94
+ response.cookies.set(cookieName, sessionToken, {
95
+ httpOnly: true,
96
+ sameSite: sameSiteValue,
97
+ path: '/',
98
+ secure: isSecure,
99
+ maxAge: 30 * 24 * 60 * 60,
100
+ ...(cookieDomain && { domain: cookieDomain }),
101
+ });
102
+ }
@@ -0,0 +1,28 @@
1
+ import type { JWT } from "next-auth/jwt";
2
+ import type { JWTPayload } from '../types';
3
+ /**
4
+ * 토큰 검증 및 디코딩
5
+ */
6
+ export declare function verifyToken(accessToken: string, secret: string): Promise<{
7
+ payload: JWTPayload;
8
+ } | null>;
9
+ /**
10
+ * payload에서 역할 추출 (서비스별)
11
+ */
12
+ export declare function extractRoleFromPayload(payload: JWTPayload, serviceId: string, defaultRole?: string): string;
13
+ /**
14
+ * payload에서 NextAuth JWT 객체 생성
15
+ */
16
+ export declare function createNextAuthJWT(payload: JWTPayload, serviceId: string): JWT;
17
+ /**
18
+ * NextAuth JWT를 인코딩된 세션 토큰으로 변환
19
+ */
20
+ export declare function encodeNextAuthToken(jwt: JWT, secret: string, maxAge?: number): Promise<string>;
21
+ /**
22
+ * 토큰이 만료되었는지 확인하는 함수
23
+ */
24
+ export declare function isTokenExpired(token: JWT | null): boolean;
25
+ /**
26
+ * 토큰이 유효한지 확인하는 함수 (만료 및 필수 필드 체크)
27
+ */
28
+ export declare function isValidToken(token: JWT | null): boolean;
@@ -0,0 +1,180 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.verifyToken = verifyToken;
37
+ exports.extractRoleFromPayload = extractRoleFromPayload;
38
+ exports.createNextAuthJWT = createNextAuthJWT;
39
+ exports.encodeNextAuthToken = encodeNextAuthToken;
40
+ exports.isTokenExpired = isTokenExpired;
41
+ exports.isValidToken = isValidToken;
42
+ const jose_1 = require("jose");
43
+ const logger_1 = require("../utils/logger");
44
+ const crypto_1 = require("../utils/crypto");
45
+ /**
46
+ * 토큰 검증 및 디코딩
47
+ */
48
+ async function verifyToken(accessToken, secret) {
49
+ try {
50
+ const secretBytes = new TextEncoder().encode(secret);
51
+ const { payload } = await (0, jose_1.jwtVerify)(accessToken, secretBytes);
52
+ if (payload && typeof payload === 'object' && payload.email) {
53
+ return { payload: payload };
54
+ }
55
+ return null;
56
+ }
57
+ catch {
58
+ return null;
59
+ }
60
+ }
61
+ /**
62
+ * payload에서 역할 추출 (서비스별)
63
+ */
64
+ function extractRoleFromPayload(payload, serviceId, defaultRole = 'ADMIN') {
65
+ const services = payload.services || [];
66
+ const service = services.find((s) => s.serviceId === serviceId);
67
+ return service?.role || payload.role || defaultRole;
68
+ }
69
+ /**
70
+ * payload에서 NextAuth JWT 객체 생성
71
+ */
72
+ function createNextAuthJWT(payload, serviceId) {
73
+ const services = payload.services || [];
74
+ const service = services.find((s) => s.serviceId === serviceId);
75
+ const effectiveRole = service?.role || payload.role || 'ADMIN';
76
+ const displayName = payload.name
77
+ || payload.decryptedEmail
78
+ || payload.maskedEmail
79
+ || 'User';
80
+ const jwt = {
81
+ id: (payload.id || payload.sub),
82
+ email: payload.email,
83
+ name: displayName,
84
+ role: effectiveRole,
85
+ services: payload.services,
86
+ phoneVerified: payload.phoneVerified ?? false,
87
+ emailVerified: payload.emailVerified ?? false,
88
+ smsVerified: payload.smsVerified ?? false,
89
+ iat: Math.floor(Date.now() / 1000),
90
+ exp: Math.floor(Date.now() / 1000) + (30 * 24 * 60 * 60), // 30일
91
+ };
92
+ if (payload.phone)
93
+ jwt.phone = payload.phone;
94
+ if (payload.isPasswordReset)
95
+ jwt.isPasswordReset = payload.isPasswordReset;
96
+ if (payload.decryptedEmail)
97
+ jwt.decryptedEmail = payload.decryptedEmail;
98
+ if (payload.decryptedPhone)
99
+ jwt.decryptedPhone = payload.decryptedPhone;
100
+ if (payload.emailHash)
101
+ jwt.emailHash = payload.emailHash;
102
+ if (payload.maskedEmail)
103
+ jwt.maskedEmail = payload.maskedEmail;
104
+ if (payload.phoneHash)
105
+ jwt.phoneHash = payload.phoneHash;
106
+ if (payload.maskedPhone)
107
+ jwt.maskedPhone = payload.maskedPhone;
108
+ if (payload.refreshToken)
109
+ jwt.refreshToken = payload.refreshToken;
110
+ if (payload.accessToken)
111
+ jwt.accessToken = payload.accessToken;
112
+ if (payload.accessTokenExpires)
113
+ jwt.accessTokenExpires = payload.accessTokenExpires;
114
+ if (payload.serviceId)
115
+ jwt.serviceId = payload.serviceId;
116
+ return jwt;
117
+ }
118
+ /**
119
+ * NextAuth JWT를 인코딩된 세션 토큰으로 변환
120
+ */
121
+ async function encodeNextAuthToken(jwt, secret, maxAge = 30 * 24 * 60 * 60) {
122
+ try {
123
+ const { encode } = await Promise.resolve().then(() => __importStar(require('next-auth/jwt')));
124
+ (0, logger_1.debugLog)('encodeNextAuthToken', 'Using next-auth/jwt encode');
125
+ const encoded = await encode({
126
+ token: jwt,
127
+ secret: secret,
128
+ maxAge: maxAge,
129
+ });
130
+ return encoded;
131
+ }
132
+ catch (error) {
133
+ (0, logger_1.debugLog)('encodeNextAuthToken', 'NextAuth encode failed, using jose EncryptJWT fallback', error);
134
+ const secretHash = await (0, crypto_1.createHashSHA256)(secret);
135
+ const keyBytes = new Uint8Array(32);
136
+ for (let i = 0; i < 32; i++) {
137
+ keyBytes[i] = parseInt(secretHash.slice(i * 2, i * 2 + 2), 16);
138
+ }
139
+ const now = Math.floor(Date.now() / 1000);
140
+ try {
141
+ const token = await new jose_1.EncryptJWT(jwt)
142
+ .setProtectedHeader({
143
+ alg: 'dir',
144
+ enc: 'A256GCM'
145
+ })
146
+ .setIssuedAt(now)
147
+ .setExpirationTime(now + maxAge)
148
+ .setJti(crypto.randomUUID())
149
+ .encrypt(keyBytes);
150
+ return token;
151
+ }
152
+ catch (encryptError) {
153
+ (0, logger_1.debugError)('encodeNextAuthToken', 'EncryptJWT also failed:', encryptError);
154
+ throw new Error(`Failed to encode NextAuth token: ${error instanceof Error ? error.message : String(error)}`);
155
+ }
156
+ }
157
+ }
158
+ /**
159
+ * 토큰이 만료되었는지 확인하는 함수
160
+ */
161
+ function isTokenExpired(token) {
162
+ if (!token)
163
+ return true;
164
+ if (token.exp && typeof token.exp === 'number' && token.exp < Math.floor(Date.now() / 1000)) {
165
+ return true;
166
+ }
167
+ return false;
168
+ }
169
+ /**
170
+ * 토큰이 유효한지 확인하는 함수 (만료 및 필수 필드 체크)
171
+ */
172
+ function isValidToken(token) {
173
+ if (!token)
174
+ return false;
175
+ if (isTokenExpired(token))
176
+ return false;
177
+ if (!token.email || !token.id)
178
+ return false;
179
+ return true;
180
+ }
@@ -0,0 +1,25 @@
1
+ import type { JWT } from "next-auth/jwt";
2
+ import type { RoleAccessConfig } from '../types';
3
+ /**
4
+ * JWT에서 서비스별 역할을 추출하는 헬퍼 함수
5
+ */
6
+ export declare function getEffectiveRole(token: JWT | null, serviceId: string): string | undefined;
7
+ /**
8
+ * 구독이 필요한 경로인지 확인하는 헬퍼 함수
9
+ */
10
+ export declare function requiresSubscription(pathname: string, role: string, subscriptionRequiredPaths: string[], systemAdminRole?: string): boolean;
11
+ /**
12
+ * 역할 기반 접근 제어 확인
13
+ */
14
+ export declare function checkRoleAccess(pathname: string, role: string, roleConfig: RoleAccessConfig): {
15
+ allowed: boolean;
16
+ message?: string;
17
+ };
18
+ /**
19
+ * 특정 역할을 가지고 있는지 확인하는 함수
20
+ */
21
+ export declare function hasRole(token: JWT | null, role: string, serviceId: string): boolean;
22
+ /**
23
+ * 여러 역할 중 하나라도 가지고 있는지 확인하는 함수
24
+ */
25
+ export declare function hasAnyRole(token: JWT | null, roles: string[], serviceId: string): boolean;
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getEffectiveRole = getEffectiveRole;
4
+ exports.requiresSubscription = requiresSubscription;
5
+ exports.checkRoleAccess = checkRoleAccess;
6
+ exports.hasRole = hasRole;
7
+ exports.hasAnyRole = hasAnyRole;
8
+ /**
9
+ * JWT에서 서비스별 역할을 추출하는 헬퍼 함수
10
+ */
11
+ function getEffectiveRole(token, serviceId) {
12
+ if (!token)
13
+ return undefined;
14
+ if (token.role) {
15
+ return token.role;
16
+ }
17
+ const services = token.services || [];
18
+ const service = services.find((s) => s.serviceId === serviceId);
19
+ return service?.role;
20
+ }
21
+ /**
22
+ * 구독이 필요한 경로인지 확인하는 헬퍼 함수
23
+ */
24
+ function requiresSubscription(pathname, role, subscriptionRequiredPaths, systemAdminRole = 'SYSTEM_ADMIN') {
25
+ if (role === systemAdminRole)
26
+ return false;
27
+ return subscriptionRequiredPaths.some(path => pathname.startsWith(path));
28
+ }
29
+ /**
30
+ * 역할 기반 접근 제어 확인
31
+ */
32
+ function checkRoleAccess(pathname, role, roleConfig) {
33
+ for (const [configRole, config] of Object.entries(roleConfig)) {
34
+ const isPathMatch = config.paths.some(path => pathname.startsWith(path));
35
+ if (isPathMatch) {
36
+ if (role === configRole || config.allowedRoles?.includes(role)) {
37
+ return { allowed: true };
38
+ }
39
+ return { allowed: false, message: config.message };
40
+ }
41
+ }
42
+ return { allowed: true };
43
+ }
44
+ /**
45
+ * 특정 역할을 가지고 있는지 확인하는 함수
46
+ */
47
+ function hasRole(token, role, serviceId) {
48
+ if (!token)
49
+ return false;
50
+ const effectiveRole = getEffectiveRole(token, serviceId);
51
+ return effectiveRole === role;
52
+ }
53
+ /**
54
+ * 여러 역할 중 하나라도 가지고 있는지 확인하는 함수
55
+ */
56
+ function hasAnyRole(token, roles, serviceId) {
57
+ if (!token)
58
+ return false;
59
+ const effectiveRole = getEffectiveRole(token, serviceId);
60
+ return roles.includes(effectiveRole || '');
61
+ }
@@ -0,0 +1,40 @@
1
+ import type { NextRequest, NextResponse } from 'next/server';
2
+ import type { JWTPayload } from '../types';
3
+ /**
4
+ * 토큰 검증 및 갱신 시도
5
+ */
6
+ export declare function verifyAndRefreshToken(req: NextRequest, secret: string, options: {
7
+ cookiePrefix: string;
8
+ serviceId: string;
9
+ isProduction: boolean;
10
+ cookieDomain?: string;
11
+ text?: string;
12
+ ssoBaseURL?: string;
13
+ authServiceKey?: string;
14
+ licenseKey: string;
15
+ forceRefresh?: boolean;
16
+ }): Promise<{
17
+ isValid: boolean;
18
+ response?: NextResponse;
19
+ payload?: JWTPayload;
20
+ error?: string;
21
+ }>;
22
+ /**
23
+ * NextAuth 토큰과 자체 토큰을 모두 확인하는 미들웨어용 함수
24
+ */
25
+ export declare function verifyAndRefreshTokenWithNextAuth(req: NextRequest, nextAuthToken: any, secret: string, options: {
26
+ cookiePrefix: string;
27
+ serviceId: string;
28
+ isProduction: boolean;
29
+ cookieDomain?: string;
30
+ text?: string;
31
+ ssoBaseURL?: string;
32
+ authServiceKey?: string;
33
+ licenseKey: string;
34
+ }): Promise<{
35
+ isValid: boolean;
36
+ response?: NextResponse;
37
+ error?: string;
38
+ payload?: JWTPayload;
39
+ token?: any;
40
+ }>;