feat(ops-auth): 이메일 로그인 세션 만료를 로그인일+15일 달력 끝까지로 변경
- OPS_SESSION_TTL_DAYS 환경변수 추가(기본 15) - .env.example 주석 갱신 Made-with: Cursor
This commit is contained in:
@@ -18,8 +18,9 @@ ADMIN_TOKEN=xavis-admin
|
||||
# SMTP_FROM=noreply@xavis.co.kr
|
||||
# 선택: 587에서 STARTTLS 강제(기본 on). 특수 서버만 0
|
||||
# SMTP_REQUIRE_TLS=1
|
||||
# 이메일 로그인 세션 만료: 해당 일자 23:59:59까지(달력은 OPS_SESSION_TZ 기준, 기본 Asia/Seoul)
|
||||
# 이메일 로그인 세션: 로그인한 달력일(OPS_SESSION_TZ) + OPS_SESSION_TTL_DAYS일의 23:59:59까지(기본 15일)
|
||||
# OPS_SESSION_TZ=Asia/Seoul
|
||||
# OPS_SESSION_TTL_DAYS=15
|
||||
PAGE_SIZE=9
|
||||
# 학습센터 동영상 파일 업로드 최대 크기(MB, 기본 500). 리버스 프록시(Nginx 등)의 client_max_body_size도 같이 늘려야 합니다.
|
||||
LECTURE_VIDEO_MAX_MB=500
|
||||
|
||||
65
ops-auth.js
65
ops-auth.js
@@ -33,30 +33,65 @@ function calendarDateKeyInTz(tsMs, tz) {
|
||||
return `${y}-${mo}-${da}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 로그인 세션 만료: OPS_SESSION_TZ(기본 Asia/Seoul)에서 해당 일자의 마지막 순간(23:59:59.999에 해당하는 epoch ms)
|
||||
*/
|
||||
function getOpsSessionExpiresAtMs(nowMs = Date.now()) {
|
||||
const tz = (process.env.OPS_SESSION_TZ || "Asia/Seoul").trim() || "Asia/Seoul";
|
||||
const cur = calendarDateKeyInTz(nowMs, tz);
|
||||
let lo = nowMs;
|
||||
let hi = nowMs + 48 * 60 * 60 * 1000;
|
||||
let guard = 0;
|
||||
while (calendarDateKeyInTz(hi, tz) === cur && guard < 400) {
|
||||
hi += 24 * 60 * 60 * 1000;
|
||||
guard++;
|
||||
/** 그레고리력 YYYY-MM-DD 문자열에 일수 더하기(타임존 무관, 달력 날짜만) */
|
||||
function addCalendarDaysToKey(key, days) {
|
||||
const n = Math.floor(Number(days) || 0);
|
||||
const [y, m, d] = key.split("-").map(Number);
|
||||
const base = new Date(Date.UTC(y, m - 1, d));
|
||||
base.setUTCDate(base.getUTCDate() + n);
|
||||
const yy = base.getUTCFullYear();
|
||||
const mm = String(base.getUTCMonth() + 1).padStart(2, "0");
|
||||
const dd = String(base.getUTCDate()).padStart(2, "0");
|
||||
return `${yy}-${mm}-${dd}`;
|
||||
}
|
||||
if (calendarDateKeyInTz(hi, tz) === cur) {
|
||||
hi = nowMs + 400 * 24 * 60 * 60 * 1000;
|
||||
|
||||
/** dateKey 달력 날짜에 속하는 임의 시각(ms) 찾기 */
|
||||
function findMsOnCalendarDay(dateKey, tz) {
|
||||
const [y, m, d] = dateKey.split("-").map(Number);
|
||||
const start = Date.UTC(y, m - 1, d, 0, 0, 0, 0);
|
||||
for (let k = -24 * 4; k < 96 * 4; k += 1) {
|
||||
const ms = start + k * 15 * 60 * 1000;
|
||||
if (calendarDateKeyInTz(ms, tz) === dateKey) return ms;
|
||||
}
|
||||
throw new Error(`findMsOnCalendarDay: ${dateKey} (${tz})`);
|
||||
}
|
||||
|
||||
/**
|
||||
* OPS_SESSION_TZ에서 dateKey 해당 날의 마지막 순간(그날 23:59:59.999에 해당하는 epoch ms)
|
||||
*/
|
||||
function getLastMsOfCalendarDayInTz(dateKey, tz) {
|
||||
const anchor = findMsOnCalendarDay(dateKey, tz);
|
||||
let lo = anchor;
|
||||
let hi = anchor + 48 * 60 * 60 * 1000;
|
||||
let guard = 0;
|
||||
while (calendarDateKeyInTz(hi, tz) === dateKey && guard < 400) {
|
||||
hi += 24 * 60 * 60 * 1000;
|
||||
guard += 1;
|
||||
}
|
||||
if (calendarDateKeyInTz(hi, tz) === dateKey) {
|
||||
hi = anchor + 400 * 24 * 60 * 60 * 1000;
|
||||
}
|
||||
while (hi - lo > 1) {
|
||||
const mid = Math.floor((lo + hi) / 2);
|
||||
if (calendarDateKeyInTz(mid, tz) === cur) lo = mid;
|
||||
if (calendarDateKeyInTz(mid, tz) === dateKey) lo = mid;
|
||||
else hi = mid;
|
||||
}
|
||||
return hi - 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 로그인 세션 만료: 로그인일(OPS_SESSION_TZ 달력) + OPS_SESSION_TTL_DAYS(기본 15)일의 마지막 순간
|
||||
*/
|
||||
function getOpsSessionExpiresAtMs(nowMs = Date.now()) {
|
||||
const tz = (process.env.OPS_SESSION_TZ || "Asia/Seoul").trim() || "Asia/Seoul";
|
||||
const raw = (process.env.OPS_SESSION_TTL_DAYS || "15").trim();
|
||||
const parsed = parseInt(raw, 10);
|
||||
const ttlDays = Number.isFinite(parsed) && parsed >= 0 ? parsed : 15;
|
||||
const loginDayKey = calendarDateKeyInTz(nowMs, tz);
|
||||
const targetKey = addCalendarDaysToKey(loginDayKey, ttlDays);
|
||||
return getLastMsOfCalendarDayInTz(targetKey, tz);
|
||||
}
|
||||
|
||||
function isOpsProd() {
|
||||
return isOpsProdMode();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user