@@ -94,28 +122,55 @@ const createErr = $derived((form as { create?: boolean; error?: string } | null)
}}
class="flex flex-wrap items-center gap-2">
-
-
-
-
-
+
+
+
+
+
{:else}
- | {pos.name} |
+ {pos.name} |
{pos.code ?? "-"} |
{pos.level} |
{/if}
-
+
diff --git a/src/routes/admin/saml-sps/+page.server.ts b/src/routes/admin/saml-sps/+page.server.ts
index fa145a5..6786dad 100644
--- a/src/routes/admin/saml-sps/+page.server.ts
+++ b/src/routes/admin/saml-sps/+page.server.ts
@@ -32,7 +32,19 @@ export const load: PageServerLoad = async ({ locals }) => {
return { sps: rows };
};
-const ATTRIBUTE_KEYS = ["email", "username", "displayName", "givenName", "familyName", "surName", "phoneNumber", "department", "team", "jobTitle", "position"] as const;
+const ATTRIBUTE_KEYS = [
+ "email",
+ "username",
+ "displayName",
+ "givenName",
+ "familyName",
+ "surName",
+ "phoneNumber",
+ "department",
+ "team",
+ "jobTitle",
+ "position",
+] as const;
function parseAllowedAttributes(raw: string): string | null {
const trimmed = raw.trim();
@@ -41,7 +53,9 @@ function parseAllowedAttributes(raw: string): string | null {
.split(",")
.map((s) => s.trim())
.filter((s) => s.length > 0)
- .filter((s): s is (typeof ATTRIBUTE_KEYS)[number] => (ATTRIBUTE_KEYS as readonly string[]).includes(s));
+ .filter((s): s is (typeof ATTRIBUTE_KEYS)[number] =>
+ (ATTRIBUTE_KEYS as readonly string[]).includes(s),
+ );
if (parts.length === 0) return null;
return JSON.stringify([...new Set(parts)]);
}
@@ -57,7 +71,9 @@ export const actions: Actions = {
const entityId = String(fd.get("entityId") ?? "").trim();
const acsUrl = String(fd.get("acsUrl") ?? "").trim();
const sloUrl = String(fd.get("sloUrl") ?? "").trim();
- const nameIdFormat = String(fd.get("nameIdFormat") ?? "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress").trim();
+ const nameIdFormat = String(
+ fd.get("nameIdFormat") ?? "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
+ ).trim();
const cert = String(fd.get("cert") ?? "").trim();
const signAssertion = fd.get("signAssertion") === "true";
const signResponse = fd.get("signResponse") === "true";
diff --git a/src/routes/admin/saml-sps/+page.svelte b/src/routes/admin/saml-sps/+page.svelte
index 59e9a3a..47df3cf 100644
--- a/src/routes/admin/saml-sps/+page.svelte
+++ b/src/routes/admin/saml-sps/+page.svelte
@@ -62,7 +62,9 @@ function parseSpMetadata() {
if (nameIdEl?.textContent) createNameIdFormat = nameIdEl.textContent.trim();
// 서명용 인증서
- const certEl = doc.querySelector("KeyDescriptor[use='signing'] X509Certificate") ?? doc.querySelector("X509Certificate");
+ const certEl =
+ doc.querySelector("KeyDescriptor[use='signing'] X509Certificate") ??
+ doc.querySelector("X509Certificate");
if (certEl?.textContent) {
const raw = certEl.textContent.trim().replace(/\s/g, "");
const lines = raw.match(/.{1,64}/g)?.join("\n") ?? raw;
@@ -97,7 +99,11 @@ function resetCreateForm() {
metaParseError = null;
}
-const createErr = $derived((form as { create?: boolean; error?: string } | null)?.create ? ((form as { error?: string } | null)?.error ?? null) : null);
+const createErr = $derived(
+ (form as { create?: boolean; error?: string } | null)?.create
+ ? ((form as { error?: string } | null)?.error ?? null)
+ : null,
+);
const globalErr = $derived(createErr ? null : ((form as { error?: string } | null)?.error ?? null));
function formatAllowedAttributes(raw: string | null): string {
@@ -147,10 +153,12 @@ const NAME_ID_OPTIONS = [
- SP 메타데이터 XML로 자동 입력 (선택)
+ SP 메타데이터 XML로 자동 입력 (선택)
{#if metaParseError}
-
+
{metaParseError}
{/if}
@@ -158,12 +166,19 @@ const NAME_ID_OPTIONS = [
bind:value={metadataXml}
rows="4"
placeholder=" 를 붙여넣으세요"
- class="block w-full rounded-md border border-gray-300 px-3 py-1.5 font-mono text-xs focus:border-blue-500 focus:outline-none">
-
+ class="block w-full rounded-md border border-gray-300 px-3 py-1.5 font-mono text-xs focus:border-blue-500 focus:outline-none"
+ >
+
{#if createErr}
-
+
{createErr}
{/if}
@@ -181,7 +196,8 @@ const NAME_ID_OPTIONS = [
}}
class="grid grid-cols-1 gap-3 sm:grid-cols-2">
-
+
-
+
-
+
-
+
-
+
@@ -277,40 +327,70 @@ const NAME_ID_OPTIONS = [
- | 이름 / Entity ID |
- ACS URL |
- 서명 |
- 상태 |
- 생성 |
- 작업 |
+ 이름 / Entity ID |
+ ACS URL |
+ 서명 |
+ 상태 |
+ 생성 |
+ 작업 |
{#if data.sps.length === 0}
- | 등록된 SAML SP 가 없습니다. |
+ 등록된 SAML SP 가 없습니다. |
{:else}
{#each data.sps as sp (sp.id)}
|
{sp.name}
- {sp.entityId}
+
+ {sp.entityId}
+
|
- {sp.acsUrl} |
+ {sp.acsUrl} |
- {#if sp.signAssertion}Assertion{/if}
- {#if sp.signResponse}Response{/if}
+ {#if sp.signAssertion}Assertion{/if}
+ {#if sp.signResponse}Response{/if}
|
-
+
{sp.enabled ? "활성" : "비활성"}
|
- {dateFormatter.format(sp.createdAt)} |
+ {dateFormatter.format(sp.createdAt)} |
-
+
-
-
|
diff --git a/src/routes/admin/signing-keys/+page.server.ts b/src/routes/admin/signing-keys/+page.server.ts
index 778cf2c..bc42c17 100644
--- a/src/routes/admin/signing-keys/+page.server.ts
+++ b/src/routes/admin/signing-keys/+page.server.ts
@@ -5,7 +5,11 @@ import { requireDbContext } from "$lib/server/auth/guards";
import { getRuntimeConfig } from "$lib/server/auth/runtime";
import { recordAuditEvent, getRequestMetadata } from "$lib/server/audit/index";
import { signingKeys } from "$lib/server/db/schema";
-import { generateRsaSigningKey, wrapPrivateKey, generateSelfSignedCert } from "$lib/server/crypto/keys";
+import {
+ generateRsaSigningKey,
+ wrapPrivateKey,
+ generateSelfSignedCert,
+} from "$lib/server/crypto/keys";
export const load: PageServerLoad = async ({ locals }) => {
const { db, tenant } = requireDbContext(locals);
@@ -47,7 +51,13 @@ export const actions: Actions = {
await db
.update(signingKeys)
.set({ active: false, rotatedAt: new Date() })
- .where(and(eq(signingKeys.tenantId, tenant.id), eq(signingKeys.active, true), isNull(signingKeys.rotatedAt)));
+ .where(
+ and(
+ eq(signingKeys.tenantId, tenant.id),
+ eq(signingKeys.active, true),
+ isNull(signingKeys.rotatedAt),
+ ),
+ );
// 새 키 생성
const { kid, publicKey, privateKey, publicJwk } = await generateRsaSigningKey();
diff --git a/src/routes/admin/signing-keys/+page.svelte b/src/routes/admin/signing-keys/+page.svelte
index 262f5a5..0d1d3a6 100644
--- a/src/routes/admin/signing-keys/+page.svelte
+++ b/src/routes/admin/signing-keys/+page.svelte
@@ -23,7 +23,12 @@ const globalErr = $derived((form as { error?: string } | null)?.error ?? null);
type="submit"
class="rounded-md bg-blue-600 px-4 py-2 text-sm font-medium text-white transition hover:bg-blue-700"
onclick={(e) => {
- if (!confirm("새 키를 생성하고 기존 활성 키를 비활성화합니다.\n계속하시겠습니까?")) e.preventDefault();
+ if (
+ !confirm(
+ "새 키를 생성하고 기존 활성 키를 비활성화합니다.\n계속하시겠습니까?",
+ )
+ )
+ e.preventDefault();
}}>
키 로테이션
@@ -49,40 +54,66 @@ const globalErr = $derived((form as { error?: string } | null)?.error ?? null);
- | KID |
- 알고리즘 |
- 용도 |
- 인증서 |
- 상태 |
- 생성일 |
- 로테이션 |
- 만료일 |
+ KID |
+ 알고리즘 |
+ 용도 |
+ 인증서 |
+ 상태 |
+ 생성일 |
+ 로테이션 |
+ 만료일 |
{#if data.keys.length === 0}
- | 등록된 서명 키가 없습니다. 키 로테이션 버튼을 눌러 첫 번째 키를 생성하세요. |
+
+ 등록된 서명 키가 없습니다. 키 로테이션 버튼을 눌러 첫 번째 키를
+ 생성하세요.
+ |
{:else}
{#each data.keys as key (key.id)}
- | {key.kid} |
+ {key.kid} |
{key.alg} |
{key.use ?? "sig"} |
{#if key.hasCert}
- 있음
+ 있음
{:else}
없음
{/if}
|
-
+
{key.active ? "활성" : "비활성"}
|
- {dateFormatter.format(key.createdAt)} |
+ {dateFormatter.format(key.createdAt)} |
{key.rotatedAt ? dateTimeFormatter.format(key.rotatedAt) : "—"}
|
diff --git a/src/routes/admin/teams/+page.svelte b/src/routes/admin/teams/+page.svelte
index 1d05599..489a12c 100644
--- a/src/routes/admin/teams/+page.svelte
+++ b/src/routes/admin/teams/+page.svelte
@@ -8,7 +8,9 @@ let showCreate = $state(false);
let editId = $state(null);
const err = $derived((form as { error?: string } | null)?.error ?? null);
-const createErr = $derived((form as { create?: boolean; error?: string } | null)?.create ? err : null);
+const createErr = $derived(
+ (form as { create?: boolean; error?: string } | null)?.create ? err : null,
+);
const STATUS_COLOR: Record = {
active: "bg-green-100 text-green-700",
@@ -20,7 +22,10 @@ const STATUS_LABEL: Record = { active: "활성", inactive: "비
팀 관리
- (showCreate = !showCreate)} class="rounded-md bg-blue-600 px-4 py-2 text-sm font-medium text-white transition hover:bg-blue-700">
+ (showCreate = !showCreate)}
+ class="rounded-md bg-blue-600 px-4 py-2 text-sm font-medium text-white transition hover:bg-blue-700">
{showCreate ? "취소" : "+ 팀 추가"}
@@ -29,7 +34,8 @@ const STATUS_LABEL: Record = { active: "활성", inactive: "비
{/if}
{#if err && !createErr}
- {err}
+
+ {err}
+
{/if}
- | 팀명 |
- 코드 |
- 소속 부서 |
- 상태 |
- 작업 |
+ 팀명 |
+ 코드 |
+ 소속 부서 |
+ 상태 |
+ 작업 |
{#if data.teams.length === 0}
- | 등록된 팀이 없습니다. |
+ | 등록된 팀이 없습니다. |
{:else}
{#each data.teams as team (team.id)}
@@ -114,42 +149,83 @@ const STATUS_LABEL: Record = { active: "활성", inactive: "비
}}
class="grid grid-cols-2 gap-2 sm:grid-cols-4">
-
-
- |