diff --git a/adminfront/src/lib/auth.ts b/adminfront/src/lib/auth.ts index 60a0e600..58224b6b 100644 --- a/adminfront/src/lib/auth.ts +++ b/adminfront/src/lib/auth.ts @@ -1,31 +1,23 @@ import { UserManager, WebStorageStateStore } from "oidc-client-ts"; import type { AuthProviderProps } from "react-oidc-context"; import { - buildAdminAuthRedirectUris, - resolveAdminPublicOrigin, -} from "./authConfig"; + buildCommonOidcRuntimeConfig, + buildCommonUserManagerSettings, +} from "../../../common/core/auth"; +import { resolveAdminPublicOrigin } from "./authConfig"; const adminPublicOrigin = resolveAdminPublicOrigin( import.meta.env.VITE_ADMIN_PUBLIC_URL, window.location.origin, ); -const adminRedirectUris = buildAdminAuthRedirectUris(adminPublicOrigin); -export const oidcConfig: AuthProviderProps = { - authority: import.meta.env.VITE_OIDC_AUTHORITY || "https://sso.hmac.kr/oidc", // Gateway Proxy URL - client_id: import.meta.env.VITE_OIDC_CLIENT_ID || "adminfront", - redirect_uri: adminRedirectUris.redirectUri, - response_type: "code", - scope: "openid offline_access profile email", // offline_access for refresh token - post_logout_redirect_uri: adminRedirectUris.postLogoutRedirectUri, - popup_redirect_uri: adminRedirectUris.popupRedirectUri, +export const oidcConfig: AuthProviderProps = buildCommonOidcRuntimeConfig({ + authority: import.meta.env.VITE_OIDC_AUTHORITY || "https://sso.hmac.kr/oidc", + clientId: import.meta.env.VITE_OIDC_CLIENT_ID || "adminfront", + origin: adminPublicOrigin, userStore: new WebStorageStateStore({ store: window.localStorage }), - automaticSilentRenew: false, -}; - -export const userManager = new UserManager({ - ...oidcConfig, - authority: oidcConfig.authority || "", - client_id: oidcConfig.client_id || "", - redirect_uri: oidcConfig.redirect_uri || "", }); + +export const userManager = new UserManager( + buildCommonUserManagerSettings(oidcConfig), +); diff --git a/adminfront/src/lib/sessionSliding.ts b/adminfront/src/lib/sessionSliding.ts index 9fd60fda..389fcef1 100644 --- a/adminfront/src/lib/sessionSliding.ts +++ b/adminfront/src/lib/sessionSliding.ts @@ -1,106 +1,6 @@ -export const SESSION_RENEW_THRESHOLD_MS = 10 * 60 * 1000; -export const SESSION_RENEW_THROTTLE_MS = 30 * 1000; - -type SlidingSessionRenewDecisionParams = { - expiresAtSec?: number | null; - nowMs: number; - isEnabled: boolean; - isAuthenticated: boolean; - isLoading: boolean; - isRenewInFlight: boolean; - lastAttemptAtMs: number; - thresholdMs?: number; - throttleMs?: number; -}; - -export function shouldAttemptSlidingSessionRenew({ - expiresAtSec, - nowMs, - isEnabled, - isAuthenticated, - isLoading, - isRenewInFlight, - lastAttemptAtMs, - thresholdMs = SESSION_RENEW_THRESHOLD_MS, - throttleMs = SESSION_RENEW_THROTTLE_MS, -}: SlidingSessionRenewDecisionParams) { - if (!isEnabled || !isAuthenticated || isLoading || isRenewInFlight) { - return false; - } - - if (typeof expiresAtSec !== "number") { - console.debug( - "[sessionSliding] expiresAtSec is not a number, skipping renew", - ); - return false; - } - - const remainingMs = expiresAtSec * 1000 - nowMs; - const remainingMin = Math.floor(remainingMs / 1000 / 60); - - if (remainingMs <= 0) { - console.debug("[sessionSliding] Session already expired, skipping renew"); - return false; - } - - if (remainingMs > thresholdMs) { - return false; - } - - if (nowMs - lastAttemptAtMs < throttleMs) { - console.debug("[sessionSliding] Throttling renewal attempt"); - return false; - } - - console.info( - `[sessionSliding] Attempting sliding session renewal. Remaining: ${remainingMin}m`, - ); - return true; -} - -export function shouldAttemptUnlimitedSessionRenew({ - expiresAtSec, - nowMs, - isEnabled, - isAuthenticated, - isLoading, - isRenewInFlight, - lastAttemptAtMs, - thresholdMs = SESSION_RENEW_THRESHOLD_MS, - throttleMs = SESSION_RENEW_THROTTLE_MS, -}: SlidingSessionRenewDecisionParams) { - if (isEnabled || !isAuthenticated || isLoading || isRenewInFlight) { - return false; - } - - if (typeof expiresAtSec !== "number") { - console.debug( - "[sessionSliding] expiresAtSec is not a number, skipping unlimited renew", - ); - return false; - } - - const remainingMs = expiresAtSec * 1000 - nowMs; - const remainingMin = Math.floor(remainingMs / 1000 / 60); - - if (remainingMs <= 0) { - console.debug( - "[sessionSliding] Session already expired, skipping unlimited renew", - ); - return false; - } - - if (remainingMs > thresholdMs) { - return false; - } - - if (nowMs - lastAttemptAtMs < throttleMs) { - console.debug("[sessionSliding] Throttling unlimited renewal attempt"); - return false; - } - - console.info( - `[sessionSliding] Attempting unlimited session renewal. Remaining: ${remainingMin}m`, - ); - return true; -} +export { + DEFAULT_SESSION_RENEW_THROTTLE_MS as SESSION_RENEW_THROTTLE_MS, + DEFAULT_SESSION_RENEW_THRESHOLD_MS as SESSION_RENEW_THRESHOLD_MS, + shouldAttemptSlidingSessionRenew, + shouldAttemptUnlimitedSessionRenew, +} from "../../../common/core/session"; diff --git a/common/core/auth/.gitkeep b/common/core/auth/.gitkeep deleted file mode 100644 index 8b137891..00000000 --- a/common/core/auth/.gitkeep +++ /dev/null @@ -1 +0,0 @@ - diff --git a/common/core/auth/index.ts b/common/core/auth/index.ts new file mode 100644 index 00000000..41e0f4e7 --- /dev/null +++ b/common/core/auth/index.ts @@ -0,0 +1,63 @@ +export const DEFAULT_OIDC_SCOPE = "openid offline_access profile email"; +export const DEFAULT_OIDC_REDIRECT_PATH = "/auth/callback"; + +export type CommonOidcConfigOptions = { + authority: string; + clientId: string; + origin?: string; + redirectPath?: string; + scope?: string; + automaticSilentRenew?: boolean; + userStore: TUserStore; +}; + +type CommonOidcRuntimeConfig = { + authority: string; + client_id: string; + redirect_uri: string; + response_type: "code"; + scope: string; + post_logout_redirect_uri: string; + popup_redirect_uri: string; + userStore: TUserStore; + automaticSilentRenew: boolean; +}; + +export function buildCommonOidcRuntimeConfig({ + authority, + clientId, + origin = window.location.origin, + redirectPath = DEFAULT_OIDC_REDIRECT_PATH, + scope = DEFAULT_OIDC_SCOPE, + automaticSilentRenew = false, + userStore, +}: CommonOidcConfigOptions): CommonOidcRuntimeConfig { + const callbackUrl = `${origin}${redirectPath}`; + + return { + authority, + client_id: clientId, + redirect_uri: callbackUrl, + response_type: "code", + scope, + post_logout_redirect_uri: origin, + popup_redirect_uri: callbackUrl, + userStore, + automaticSilentRenew, + }; +} + +export function buildCommonUserManagerSettings< + TConfig extends { + authority?: string; + client_id?: string; + redirect_uri?: string; + }, +>(config: TConfig) { + return { + ...config, + authority: config.authority || "", + client_id: config.client_id || "", + redirect_uri: config.redirect_uri || "", + }; +} diff --git a/common/core/session/.gitkeep b/common/core/session/.gitkeep deleted file mode 100644 index 8b137891..00000000 --- a/common/core/session/.gitkeep +++ /dev/null @@ -1 +0,0 @@ - diff --git a/common/core/session/index.ts b/common/core/session/index.ts new file mode 100644 index 00000000..18ef3e07 --- /dev/null +++ b/common/core/session/index.ts @@ -0,0 +1,65 @@ +export const DEFAULT_SESSION_RENEW_THRESHOLD_MS = 10 * 60 * 1000; +export const DEFAULT_SESSION_RENEW_THROTTLE_MS = 30 * 1000; + +export type SessionRenewDecisionParams = { + expiresAtSec?: number | null; + nowMs: number; + isEnabled: boolean; + isAuthenticated: boolean; + isLoading: boolean; + isRenewInFlight: boolean; + lastAttemptAtMs: number; + thresholdMs?: number; + throttleMs?: number; +}; + +function hasRenewPreconditions({ + isAuthenticated, + isLoading, + isRenewInFlight, +}: SessionRenewDecisionParams) { + return isAuthenticated && !isLoading && !isRenewInFlight; +} + +function isRenewWindowOpen({ + expiresAtSec, + nowMs, + lastAttemptAtMs, + thresholdMs = DEFAULT_SESSION_RENEW_THRESHOLD_MS, + throttleMs = DEFAULT_SESSION_RENEW_THROTTLE_MS, +}: SessionRenewDecisionParams) { + if (typeof expiresAtSec !== "number") { + return false; + } + + const remainingMs = expiresAtSec * 1000 - nowMs; + if (remainingMs <= 0 || remainingMs > thresholdMs) { + return false; + } + + if (nowMs - lastAttemptAtMs < throttleMs) { + return false; + } + + return true; +} + +export function shouldAttemptSlidingSessionRenew( + params: SessionRenewDecisionParams, +) { + if (!params.isEnabled || !hasRenewPreconditions(params)) { + return false; + } + + return isRenewWindowOpen(params); +} + +export function shouldAttemptUnlimitedSessionRenew( + params: SessionRenewDecisionParams, +) { + if (params.isEnabled || !hasRenewPreconditions(params)) { + return false; + } + + return isRenewWindowOpen(params); +} diff --git a/devfront/src/lib/auth.ts b/devfront/src/lib/auth.ts index 815d5c32..62768900 100644 --- a/devfront/src/lib/auth.ts +++ b/devfront/src/lib/auth.ts @@ -1,32 +1,23 @@ import { UserManager, WebStorageStateStore } from "oidc-client-ts"; import type { AuthProviderProps } from "react-oidc-context"; import { - buildDevFrontAuthRedirectUris, - resolveDevFrontPublicOrigin, -} from "./authConfig"; + buildCommonOidcRuntimeConfig, + buildCommonUserManagerSettings, +} from "../../../common/core/auth"; +import { resolveDevFrontPublicOrigin } from "./authConfig"; const devFrontPublicOrigin = resolveDevFrontPublicOrigin( import.meta.env.VITE_DEVFRONT_PUBLIC_URL, window.location.origin, ); -const devFrontRedirectUris = - buildDevFrontAuthRedirectUris(devFrontPublicOrigin); -export const oidcConfig: AuthProviderProps = { - authority: import.meta.env.VITE_OIDC_AUTHORITY || "https://sso.hmac.kr/oidc", // Gateway Proxy URL - client_id: import.meta.env.VITE_OIDC_CLIENT_ID || "devfront", - redirect_uri: devFrontRedirectUris.redirectUri, - response_type: "code", - scope: "openid offline_access profile email", // offline_access for refresh token - post_logout_redirect_uri: devFrontRedirectUris.postLogoutRedirectUri, - popup_redirect_uri: devFrontRedirectUris.popupRedirectUri, +export const oidcConfig: AuthProviderProps = buildCommonOidcRuntimeConfig({ + authority: import.meta.env.VITE_OIDC_AUTHORITY || "https://sso.hmac.kr/oidc", + clientId: import.meta.env.VITE_OIDC_CLIENT_ID || "devfront", + origin: devFrontPublicOrigin, userStore: new WebStorageStateStore({ store: window.localStorage }), - automaticSilentRenew: false, -}; - -export const userManager = new UserManager({ - ...oidcConfig, - authority: oidcConfig.authority || "", - client_id: oidcConfig.client_id || "", - redirect_uri: oidcConfig.redirect_uri || "", }); + +export const userManager = new UserManager( + buildCommonUserManagerSettings(oidcConfig), +); diff --git a/devfront/src/lib/sessionSliding.ts b/devfront/src/lib/sessionSliding.ts index 831ef8e8..389fcef1 100644 --- a/devfront/src/lib/sessionSliding.ts +++ b/devfront/src/lib/sessionSliding.ts @@ -1,76 +1,6 @@ -export const SESSION_RENEW_THRESHOLD_MS = 10 * 60 * 1000; -export const SESSION_RENEW_THROTTLE_MS = 30 * 1000; - -type SlidingSessionRenewDecisionParams = { - expiresAtSec?: number | null; - nowMs: number; - isEnabled: boolean; - isAuthenticated: boolean; - isLoading: boolean; - isRenewInFlight: boolean; - lastAttemptAtMs: number; - thresholdMs?: number; - throttleMs?: number; -}; - -export function shouldAttemptSlidingSessionRenew({ - expiresAtSec, - nowMs, - isEnabled, - isAuthenticated, - isLoading, - isRenewInFlight, - lastAttemptAtMs, - thresholdMs = SESSION_RENEW_THRESHOLD_MS, - throttleMs = SESSION_RENEW_THROTTLE_MS, -}: SlidingSessionRenewDecisionParams) { - if (!isEnabled || !isAuthenticated || isLoading || isRenewInFlight) { - return false; - } - - if (typeof expiresAtSec !== "number") { - return false; - } - - const remainingMs = expiresAtSec * 1000 - nowMs; - if (remainingMs <= 0 || remainingMs > thresholdMs) { - return false; - } - - if (nowMs - lastAttemptAtMs < throttleMs) { - return false; - } - - return true; -} - -export function shouldAttemptUnlimitedSessionRenew({ - expiresAtSec, - nowMs, - isEnabled, - isAuthenticated, - isLoading, - isRenewInFlight, - lastAttemptAtMs, - thresholdMs = SESSION_RENEW_THRESHOLD_MS, - throttleMs = SESSION_RENEW_THROTTLE_MS, -}: SlidingSessionRenewDecisionParams) { - if (isEnabled || !isAuthenticated || isLoading || isRenewInFlight) { - return false; - } - - if (typeof expiresAtSec !== "number") { - return false; - } - - const remainingMs = expiresAtSec * 1000 - nowMs; - if (remainingMs <= 0 || remainingMs > thresholdMs) { - return false; - } - - if (nowMs - lastAttemptAtMs < throttleMs) { - return false; - } - - return true; -} +export { + DEFAULT_SESSION_RENEW_THROTTLE_MS as SESSION_RENEW_THROTTLE_MS, + DEFAULT_SESSION_RENEW_THRESHOLD_MS as SESSION_RENEW_THRESHOLD_MS, + shouldAttemptSlidingSessionRenew, + shouldAttemptUnlimitedSessionRenew, +} from "../../../common/core/session"; diff --git a/orgfront/src/lib/auth.ts b/orgfront/src/lib/auth.ts index 06d2762c..49cadbfd 100644 --- a/orgfront/src/lib/auth.ts +++ b/orgfront/src/lib/auth.ts @@ -1,33 +1,24 @@ import { UserManager, WebStorageStateStore } from "oidc-client-ts"; import type { AuthProviderProps } from "react-oidc-context"; import { - buildOrgFrontAuthRedirectUris, - resolveOrgFrontPublicOrigin, -} from "./authConfig"; + buildCommonOidcRuntimeConfig, + buildCommonUserManagerSettings, +} from "../../../common/core/auth"; +import { resolveOrgFrontPublicOrigin } from "./authConfig"; const orgFrontPublicOrigin = resolveOrgFrontPublicOrigin( import.meta.env.VITE_ORGFRONT_PUBLIC_URL, window.location.origin, ); -const orgFrontRedirectUris = - buildOrgFrontAuthRedirectUris(orgFrontPublicOrigin); -export const oidcConfig: AuthProviderProps = { +export const oidcConfig: AuthProviderProps = buildCommonOidcRuntimeConfig({ authority: - import.meta.env.VITE_OIDC_AUTHORITY || "http://localhost:5000/oidc", // Gateway Proxy URL - client_id: import.meta.env.VITE_OIDC_CLIENT_ID || "orgfront", - redirect_uri: orgFrontRedirectUris.redirectUri, - response_type: "code", - scope: "openid offline_access profile email", // offline_access for refresh token - post_logout_redirect_uri: orgFrontRedirectUris.postLogoutRedirectUri, - popup_redirect_uri: orgFrontRedirectUris.popupRedirectUri, + import.meta.env.VITE_OIDC_AUTHORITY || "http://localhost:5000/oidc", + clientId: import.meta.env.VITE_OIDC_CLIENT_ID || "orgfront", + origin: orgFrontPublicOrigin, userStore: new WebStorageStateStore({ store: window.localStorage }), - automaticSilentRenew: false, -}; - -export const userManager = new UserManager({ - ...oidcConfig, - authority: oidcConfig.authority || "", - client_id: oidcConfig.client_id || "", - redirect_uri: oidcConfig.redirect_uri || "", }); + +export const userManager = new UserManager( + buildCommonUserManagerSettings(oidcConfig), +); diff --git a/orgfront/src/lib/sessionSliding.ts b/orgfront/src/lib/sessionSliding.ts index be152778..6bb88ecd 100644 --- a/orgfront/src/lib/sessionSliding.ts +++ b/orgfront/src/lib/sessionSliding.ts @@ -1,76 +1,27 @@ +import { + DEFAULT_SESSION_RENEW_THROTTLE_MS, + shouldAttemptSlidingSessionRenew as shouldAttemptSlidingSessionRenewBase, + shouldAttemptUnlimitedSessionRenew as shouldAttemptUnlimitedSessionRenewBase, + type SessionRenewDecisionParams, +} from "../../../common/core/session"; + +export const SESSION_RENEW_THROTTLE_MS = DEFAULT_SESSION_RENEW_THROTTLE_MS; export const SESSION_RENEW_THRESHOLD_MS = 5 * 60 * 1000; -export const SESSION_RENEW_THROTTLE_MS = 30 * 1000; -type SlidingSessionRenewDecisionParams = { - expiresAtSec?: number | null; - nowMs: number; - isEnabled: boolean; - isAuthenticated: boolean; - isLoading: boolean; - isRenewInFlight: boolean; - lastAttemptAtMs: number; - thresholdMs?: number; - throttleMs?: number; -}; - -export function shouldAttemptSlidingSessionRenew({ - expiresAtSec, - nowMs, - isEnabled, - isAuthenticated, - isLoading, - isRenewInFlight, - lastAttemptAtMs, - thresholdMs = SESSION_RENEW_THRESHOLD_MS, - throttleMs = SESSION_RENEW_THROTTLE_MS, -}: SlidingSessionRenewDecisionParams) { - if (!isEnabled || !isAuthenticated || isLoading || isRenewInFlight) { - return false; - } - - if (typeof expiresAtSec !== "number") { - return false; - } - - const remainingMs = expiresAtSec * 1000 - nowMs; - if (remainingMs <= 0 || remainingMs > thresholdMs) { - return false; - } - - if (nowMs - lastAttemptAtMs < throttleMs) { - return false; - } - - return true; +export function shouldAttemptSlidingSessionRenew( + params: SessionRenewDecisionParams, +) { + return shouldAttemptSlidingSessionRenewBase({ + ...params, + thresholdMs: params.thresholdMs ?? SESSION_RENEW_THRESHOLD_MS, + }); } -export function shouldAttemptUnlimitedSessionRenew({ - expiresAtSec, - nowMs, - isEnabled, - isAuthenticated, - isLoading, - isRenewInFlight, - lastAttemptAtMs, - thresholdMs = SESSION_RENEW_THRESHOLD_MS, - throttleMs = SESSION_RENEW_THROTTLE_MS, -}: SlidingSessionRenewDecisionParams) { - if (isEnabled || !isAuthenticated || isLoading || isRenewInFlight) { - return false; - } - - if (typeof expiresAtSec !== "number") { - return false; - } - - const remainingMs = expiresAtSec * 1000 - nowMs; - if (remainingMs <= 0 || remainingMs > thresholdMs) { - return false; - } - - if (nowMs - lastAttemptAtMs < throttleMs) { - return false; - } - - return true; +export function shouldAttemptUnlimitedSessionRenew( + params: SessionRenewDecisionParams, +) { + return shouldAttemptUnlimitedSessionRenewBase({ + ...params, + thresholdMs: params.thresholdMs ?? SESSION_RENEW_THRESHOLD_MS, + }); }