From d94e7d6e2601d1cfd3338a15ad3311c7b4d6dfa6 Mon Sep 17 00:00:00 2001 From: Henry Jang Date: Sun, 5 Jul 2026 19:03:50 +0900 Subject: [PATCH 1/8] =?UTF-8?q?feat(oidc):=20groups=20scope=20+=20organiza?= =?UTF-8?q?tion=20=ED=99=94=EC=9D=B4=ED=8A=B8=EB=A6=AC=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=EB=B3=B5=EA=B5=AC=20+=20per-client=20rate-limit=20+=20useri?= =?UTF-8?q?nfo=20Vary?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - groups scope: 활성 조직 멤버십(department/team/part code|name)을 groups 배열로 매핑 (membershipToGroups). token id_token·userinfo 공유. discovery scopes/claims_supported 에 groups 추가. - organization scope 를 admin ALLOWED_OIDC_SCOPES 에 추가(discovery/userinfo 는 이미 처리했으나 admin UI 화이트리스트 누락으로 클라이언트 설정 불가였음). - token 엔드포인트에 per-client rate-limit(token-client:${clientId}, 60/분) 추가 — 남용 client 격리. - userinfo 응답에 Vary: Authorization 헤더(캐시 오염 방지). Co-Authored-By: Claude Opus 4.8 (1M context) --- src/lib/server/org/membership.ts | 18 ++++++ .../openid-configuration/+server.ts | 3 +- src/routes/admin/oidc-clients/+page.server.ts | 2 +- src/routes/oidc/token/+server.ts | 28 +++++++++ src/routes/oidc/userinfo/+server.ts | 62 +++++++++++-------- 5 files changed, 84 insertions(+), 29 deletions(-) diff --git a/src/lib/server/org/membership.ts b/src/lib/server/org/membership.ts index 5027a59..cbada9e 100644 --- a/src/lib/server/org/membership.ts +++ b/src/lib/server/org/membership.ts @@ -39,6 +39,24 @@ export interface UserMembership { primaryJobTitle: string | null; } +/** + * 멤버십을 OIDC `groups` scope 용 문자열 배열로 매핑한다. + * 부서 → 팀 → 파트 순으로, 각 항목은 code 우선(없으면 name)으로 라벨링하고 중복은 제거한다. + * token 의 id_token 과 userinfo 응답이 동일한 값을 쓰도록 공용화한다. + */ +export function membershipToGroups(membership: UserMembership): string[] { + const groups: string[] = []; + const push = (code: string | null, name: string): void => { + const label = (code && code.trim()) || name; + if (label) groups.push(label); + }; + for (const d of membership.departments) push(d.code, d.name); + for (const t of membership.teams) push(t.code, t.name); + for (const p of membership.parts) push(p.code, p.name); + // 순서 유지하며 중복 제거. + return [...new Set(groups)]; +} + /** 현재 소속 중인 부서/팀/파트 소속 정보를 한 번에 조회 */ export async function getUserMembership(db: DB, userId: string): Promise { // 현재 소속 부서 (endedAt IS NULL) diff --git a/src/routes/.well-known/openid-configuration/+server.ts b/src/routes/.well-known/openid-configuration/+server.ts index b804c8e..d18c98d 100644 --- a/src/routes/.well-known/openid-configuration/+server.ts +++ b/src/routes/.well-known/openid-configuration/+server.ts @@ -17,7 +17,7 @@ export const GET: RequestHandler = async ({ locals, url }) => { revocation_endpoint: `${issuer}/oidc/revoke`, introspection_endpoint_auth_methods_supported: ["client_secret_basic", "client_secret_post"], revocation_endpoint_auth_methods_supported: ["client_secret_basic", "client_secret_post"], - scopes_supported: ["openid", "profile", "email", "phone", "organization", "offline_access"], + scopes_supported: ["openid", "profile", "email", "phone", "organization", "groups", "offline_access"], response_types_supported: ["code"], grant_types_supported: ["authorization_code", "refresh_token"], subject_types_supported: ["public"], @@ -54,6 +54,7 @@ export const GET: RequestHandler = async ({ locals, url }) => { "team", "position", "job_title", + "groups", "roles", "roles_label", ], diff --git a/src/routes/admin/oidc-clients/+page.server.ts b/src/routes/admin/oidc-clients/+page.server.ts index 248bfea..70ca5a3 100644 --- a/src/routes/admin/oidc-clients/+page.server.ts +++ b/src/routes/admin/oidc-clients/+page.server.ts @@ -23,7 +23,7 @@ function generateClientSecret(): string { const ALLOWED_TOKEN_AUTH_METHODS = ["client_secret_basic", "client_secret_post", "none"] as const; type TokenAuthMethod = (typeof ALLOWED_TOKEN_AUTH_METHODS)[number]; -const ALLOWED_OIDC_SCOPES = ["openid", "profile", "email", "address", "phone", "offline_access", "groups"] as const; +const ALLOWED_OIDC_SCOPES = ["openid", "profile", "email", "address", "phone", "offline_access", "organization", "groups"] as const; /** * 단일 redirect URI / post-logout / channel logout URL 검증. diff --git a/src/routes/oidc/token/+server.ts b/src/routes/oidc/token/+server.ts index 380bc11..b55a0fd 100644 --- a/src/routes/oidc/token/+server.ts +++ b/src/routes/oidc/token/+server.ts @@ -12,6 +12,7 @@ import { verifyPkce } from "$lib/server/oidc/pkce"; import { issueRefreshToken, rotateRefreshToken, revokeRefreshTokenFamily } from "$lib/server/oidc/refresh"; import { generateAccessToken, getActiveSigningKey, signJwt } from "$lib/server/crypto/keys"; import { getActiveAssignment, parseAssignmentAttributes } from "$lib/server/access/service-permissions"; +import { getUserMembership, membershipToGroups } from "$lib/server/org/membership"; import { resolveIssuerUrl } from "$lib/server/auth/runtime"; import type { DB } from "$lib/server/db"; import type { OidcClientRecord } from "$lib/server/oidc/client"; @@ -117,6 +118,13 @@ async function buildTokens(params: BuildTokenParams): Promise<{ idToken: string; } } + // groups scope — 활성 조직 멤버십을 code(없으면 name) 문자열 배열로 매핑. + // userinfo 응답과 동일 로직(membershipToGroups)을 공유한다. 표준 claim 이므로 assignment 머지 이후에 설정. + if (scopes.has("groups")) { + const membership = await getUserMembership(db, user.id); + idTokenPayload.groups = membershipToGroups(membership); + } + const idToken = await signJwt(idTokenPayload, signingKey.privateKey, signingKey.kid); const accessToken = await generateAccessToken( @@ -242,6 +250,26 @@ export const POST: RequestHandler = async (event) => { } } + // per-client 레이트 리밋: 인증 성공한 클라이언트당 60회/분. + // IP 기반 리밋과 별개로, 자격증명을 아는 남용 클라이언트를 격리한다. + const clientRl = await checkRateLimit(db, `token-client:${clientId}`, { windowMs: 60 * 1000, limit: 60 }); + if (!clientRl.allowed) { + await recordTokenFailure(clientId, "rate_limit_exceeded", "client 요청이 너무 많습니다"); + return new Response( + JSON.stringify({ + error: "rate_limit_exceeded", + error_description: "요청이 너무 많습니다. 잠시 후 다시 시도해 주세요.", + }), + { + status: 429, + headers: { + "Content-Type": "application/json", + "Retry-After": String(Math.ceil(clientRl.retryAfterMs / 1000)), + }, + }, + ); + } + if (!clientAllowsGrant(client, grantType)) { await recordTokenFailure(clientId, "unauthorized_client", `클라이언트에 허용되지 않은 grant_type: ${grantType}`); return tokenError("unauthorized_client", "이 클라이언트에 허용되지 않은 grant_type 입니다."); diff --git a/src/routes/oidc/userinfo/+server.ts b/src/routes/oidc/userinfo/+server.ts index e7e399b..6ce3a42 100644 --- a/src/routes/oidc/userinfo/+server.ts +++ b/src/routes/oidc/userinfo/+server.ts @@ -4,7 +4,7 @@ import { and, eq } from "drizzle-orm"; import { requireDbContext } from "$lib/server/auth/guards"; import { oidcClients, users } from "$lib/server/db/schema"; import { verifyAccessToken } from "$lib/server/crypto/keys"; -import { getUserMembership } from "$lib/server/org/membership"; +import { getUserMembership, membershipToGroups } from "$lib/server/org/membership"; import { getActiveAssignment, parseAssignmentAttributes } from "$lib/server/access/service-permissions"; const RESERVED_USERINFO_CLAIMS = new Set(["sub", "iss", "aud", "iat", "exp", "auth_time"]); @@ -110,39 +110,47 @@ async function handleUserinfo(locals: App.Locals, request: Request): Promise ({ - id: d.id, - name: d.name, - code: d.code, - is_primary: d.isPrimary, - job_title: d.jobTitle, - position: d.position - ? { - id: d.position.id, - name: d.position.name, - code: d.position.code, - level: d.position.level, - } - : null, - })); - response.team = membership.teams.map((t) => ({ - id: t.id, - name: t.name, - code: t.code, - department: t.departmentName, - is_primary: t.isPrimary, - job_title: t.jobTitle, - })); - response.position = membership.primaryPosition?.name ?? null; - response.job_title = membership.primaryJobTitle ?? null; + + if (scopes.has("groups")) { + response.groups = membershipToGroups(membership); + } + + if (scopes.has("organization")) { + response.department = membership.departments.map((d) => ({ + id: d.id, + name: d.name, + code: d.code, + is_primary: d.isPrimary, + job_title: d.jobTitle, + position: d.position + ? { + id: d.position.id, + name: d.position.name, + code: d.position.code, + level: d.position.level, + } + : null, + })); + response.team = membership.teams.map((t) => ({ + id: t.id, + name: t.name, + code: t.code, + department: t.departmentName, + is_primary: t.isPrimary, + job_title: t.jobTitle, + })); + response.position = membership.primaryPosition?.name ?? null; + response.job_title = membership.primaryJobTitle ?? null; + } } return json(response, { headers: { "Cache-Control": "no-store, private", Pragma: "no-cache", + Vary: "Authorization", }, }); } From 00f73a5eb47a3e1d9c640e6c35fc4cd9a93aa1f2 Mon Sep 17 00:00:00 2001 From: Henry Jang Date: Sun, 5 Jul 2026 19:03:51 +0900 Subject: [PATCH 2/8] =?UTF-8?q?fix(saml):=20metadata=20WantAuthnRequestsSi?= =?UTF-8?q?gned=20SP=20=EB=B0=98=EC=98=81=20+=20Vary=20=ED=97=A4=EB=8D=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - generateIdpMetadataXml 에 wantAuthnRequestsSigned 파라미터 추가(하드코딩 false 제거). ?sp= 쿼리 시 findSp 로 SP 별 값 반영 — 동작(런타임)과 광고(metadata) 불일치 해소. - saml/metadata 응답에 Vary 헤더(테넌트/SP별 캐시 키 분리, Cache-Control 유지). Co-Authored-By: Claude Opus 4.8 (1M context) --- src/lib/server/saml/metadata.ts | 10 ++++++++-- src/routes/saml/metadata/+server.ts | 12 ++++++++++-- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/lib/server/saml/metadata.ts b/src/lib/server/saml/metadata.ts index 925601b..abf664d 100644 --- a/src/lib/server/saml/metadata.ts +++ b/src/lib/server/saml/metadata.ts @@ -18,7 +18,13 @@ function xmlEscape(str: string): string { return str.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """); } -export async function generateIdpMetadataXml(db: DB, tenantId: string, issuerUrl: string): Promise { +/** + * IdP SAML metadata XML 생성. + * @param wantAuthnRequestsSigned SP 컨텍스트가 있을 때 해당 SP의 값을 광고한다. + * SP 컨텍스트가 없는 IdP 전역 metadata 는 기본 false 를 유지한다. + * (실제 SSO 는 SP별 DB 필드를 런타임에 강제하므로 metadata 는 광고용.) + */ +export async function generateIdpMetadataXml(db: DB, tenantId: string, issuerUrl: string, wantAuthnRequestsSigned = false): Promise { const [keyRow] = await db .select({ certPem: signingKeys.certPem }) .from(signingKeys) @@ -47,7 +53,7 @@ export async function generateIdpMetadataXml(db: DB, tenantId: string, issuerUrl entityID="${xmlEscape(issuerUrl)}" validUntil="${validUntil}"> ${keyDescriptor} { +export const GET: RequestHandler = async ({ locals, platform, url }) => { const { db, tenant } = requireDbContext(locals); const config = getRuntimeConfig(platform); @@ -12,12 +13,19 @@ export const GET: RequestHandler = async ({ locals, platform }) => { throw error(503, "IDP_ISSUER_URL 미설정"); } - const xml = await generateIdpMetadataXml(db, tenant.id, config.issuerUrl); + // SP 컨텍스트가 주어지면(`?sp=`) 해당 SP의 wantAuthnRequestsSigned 를 광고에 반영. + // 없으면 IdP 전역 metadata 로 기본(false) 동작 유지. + const spEntityId = url.searchParams.get("sp"); + const sp = spEntityId ? await findSp(db, tenant.id, spEntityId) : null; + + const xml = await generateIdpMetadataXml(db, tenant.id, config.issuerUrl, sp?.wantAuthnRequestsSigned ?? false); return new Response(xml, { headers: { "Content-Type": "application/samlmetadata+xml; charset=utf-8", "Cache-Control": "public, max-age=3600", + // 테넌트/SP 별 응답이 공유 캐시에서 섞이지 않도록 캐시 키를 분리한다. + Vary: "Accept, Host", }, }); }; From 06abbd4218da40988ad660cc3a05aa574541589b Mon Sep 17 00:00:00 2001 From: Henry Jang Date: Sun, 5 Jul 2026 19:03:51 +0900 Subject: [PATCH 3/8] =?UTF-8?q?fix:=20TOTP=20enroll=20catch=20=EB=A5=BC=20?= =?UTF-8?q?unique=20=EC=9C=84=EB=B0=98=EC=9C=BC=EB=A1=9C=20=ED=95=9C?= =?UTF-8?q?=EC=A0=95=20+=20positions=20level=20=EB=B9=88=EB=AC=B8=EC=9E=90?= =?UTF-8?q?=EC=97=B4=20=EA=B1=B0=EB=B6=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - lib/server/db/errors.ts: 방언 무관 isUniqueViolation(err) 헬퍼(sqlite/d1 UNIQUE, pg 23505, mysql 1062). - enroll/confirm: unique 위반만 409, 그 외 DB 에러는 재던져 500 전파(관측성 회복). - admin/schemas.ts intField: 빈 문자열/공백을 undefined 로 정규화해 조용한 0 저장 대신 검증 실패. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/lib/server/admin/schemas.ts | 8 +++- src/lib/server/db/errors.ts | 37 +++++++++++++++++++ src/routes/api/totp/enroll/confirm/+server.ts | 11 ++++-- 3 files changed, 51 insertions(+), 5 deletions(-) create mode 100644 src/lib/server/db/errors.ts diff --git a/src/lib/server/admin/schemas.ts b/src/lib/server/admin/schemas.ts index 060c50c..ff00778 100644 --- a/src/lib/server/admin/schemas.ts +++ b/src/lib/server/admin/schemas.ts @@ -26,8 +26,12 @@ export const optionalText = z /** status enum (기본 active). */ export const statusField = z.enum(["active", "inactive"]).default("active"); -/** 정수 필드(level): 유효하지 않으면 실패. */ -export const intField = (message: string) => z.coerce.number(message).int(message); +/** + * 정수 필드(level): 유효하지 않으면 실패. + * 빈 문자열/공백은 undefined 로 정규화해 누락과 동일하게 거부한다 — + * `z.coerce.number("")` 가 0 으로 조용히 통과하던 비일관을 막는다. + */ +export const intField = (message: string) => z.preprocess((v) => (typeof v === "string" && v.trim() === "" ? undefined : v), z.coerce.number(message).int(message)); /** displayOrder: 유효하지 않거나 빈값이면 0 (기존 parseInt isNaN→0 동작 보존). */ export const displayOrderField = z.coerce.number().int().catch(0); diff --git a/src/lib/server/db/errors.ts b/src/lib/server/db/errors.ts new file mode 100644 index 0000000..39c75ac --- /dev/null +++ b/src/lib/server/db/errors.ts @@ -0,0 +1,37 @@ +/** + * DB 방언 무관 에러 분류 헬퍼. + * + * drizzle 은 방언별 드라이버의 원본 에러를 그대로 던지므로, unique 제약 위반 여부는 + * 각 드라이버가 노출하는 message/code 문자열의 마커로 판별한다. + * - sqlite / d1 / libSQL : "UNIQUE constraint failed" (message) + * - postgres : code "23505" 또는 "duplicate key value" (message) + * - mysql : errno 1062 / code "ER_DUP_ENTRY" / "Duplicate entry" (message) + */ +export function isUniqueViolation(err: unknown): boolean { + if (err == null) return false; + + const parts: string[] = []; + if (typeof err === "string") { + parts.push(err); + } else if (typeof err === "object") { + const e = err as { message?: unknown; code?: unknown; cause?: unknown }; + if (typeof e.message === "string") parts.push(e.message); + if (typeof e.code === "string") parts.push(e.code); + if (typeof e.code === "number") parts.push(String(e.code)); + // 일부 드라이버는 원본 에러를 cause 로 래핑한다. + if (e.cause != null && e.cause !== err && isUniqueViolation(e.cause)) return true; + } + + if (parts.length === 0) return false; + const haystack = parts.join(" "); + const upper = haystack.toUpperCase(); + + // sqlite / d1 + if (upper.includes("UNIQUE")) return true; + // postgres + if (haystack.includes("23505") || upper.includes("DUPLICATE KEY")) return true; + // mysql + if (haystack.includes("1062") || upper.includes("ER_DUP_ENTRY") || upper.includes("DUPLICATE ENTRY")) return true; + + return false; +} diff --git a/src/routes/api/totp/enroll/confirm/+server.ts b/src/routes/api/totp/enroll/confirm/+server.ts index c2083d9..04ac5c8 100644 --- a/src/routes/api/totp/enroll/confirm/+server.ts +++ b/src/routes/api/totp/enroll/confirm/+server.ts @@ -7,6 +7,7 @@ import { encryptTotpSecret, generateBackupCodes, hashBackupCode, verifyTotp } fr import { checkRateLimit } from "$lib/server/ratelimit"; import { credentials, users } from "$lib/server/db/schema"; import { type DB, DB_DIALECT } from "$lib/server/db"; +import { isUniqueViolation } from "$lib/server/db/errors"; /** * Phase 7.3 — TOTP enrollment confirm. @@ -101,10 +102,14 @@ export const POST: RequestHandler = async ({ request, locals }) => { await buildBackupInsert(tx); }); } - } catch { - // credentials_totp_owner_uidx UNIQUE 위반(동시 이중 등록) 또는 기타 DB 에러 → 409. + } catch (err) { + // credentials_totp_owner_uidx UNIQUE 위반(동시 이중 등록)일 때만 409 로 매핑한다. // 사전 SELECT 를 통과한 두 동시 요청 중 두 번째가 여기서 안전하게 거부된다. - throw error(409, "TOTP already enrolled for this user"); + // 그 외 DB 에러는 재던져 500 으로 전파 — 관측성을 잃지 않는다. + if (isUniqueViolation(err)) { + throw error(409, "TOTP already enrolled for this user"); + } + throw err; } return json({ ok: true, backupCodes }); From a0bf2181804cc77d2dcc4a4cbc0bd8ac25a409fd Mon Sep 17 00:00:00 2001 From: Henry Jang Date: Sun, 5 Jul 2026 19:09:35 +0900 Subject: [PATCH 4/8] =?UTF-8?q?feat(oidc):=20address=20scope=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20=E2=80=94=20users=20=EC=A3=BC=EC=86=8C=20=EC=BB=AC?= =?UTF-8?q?=EB=9F=BC=20+=20=ED=81=B4=EB=A0=88=EC=9E=84=20+=20admin=20?= =?UTF-8?q?=EC=9E=85=EB=A0=A5=20UI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - users 3방언 스키마에 nullable 주소 5컬럼(address_street/locality/region/postal_code/country). - lib/oidc/claims.ts: buildAddressClaim — 하위 필드 매핑 + formatted 조합, 전부 null 이면 클레임 생략. token id_token·userinfo 공유. address scope 요청 시에만 발급. - discovery scopes/claims_supported 에 address 추가. - admin users/[id] updateProfile 에 주소 5필드 저장(tenant 스코프) + 편집 폼 입력. - 마이그레이션 생성만(d1 0020, pg/mysql/sqlite 0003) — 적용은 사용자. vitest 54 passed, svelte-check 0 errors, build 성공. Co-Authored-By: Claude Opus 4.8 (1M context) --- drizzle/0020_cool_firebrand.sql | 5 + drizzle/meta/0020_snapshot.json | 3793 +++++++++++++++ drizzle/meta/_journal.json | 7 + drizzle/mysql/0003_shiny_abomination.sql | 5 + drizzle/mysql/meta/0003_snapshot.json | 3974 +++++++++++++++ drizzle/mysql/meta/_journal.json | 7 + drizzle/pg/0003_charming_the_fury.sql | 5 + drizzle/pg/meta/0003_snapshot.json | 4256 +++++++++++++++++ drizzle/pg/meta/_journal.json | 7 + drizzle/sqlite/0003_white_speed_demon.sql | 5 + drizzle/sqlite/meta/0003_snapshot.json | 3793 +++++++++++++++ drizzle/sqlite/meta/_journal.json | 7 + src/lib/i18n/ko.json | 5 + src/lib/server/db/schema.mysql.ts | 7 + src/lib/server/db/schema.pg.ts | 6 + src/lib/server/db/schema.sqlite.ts | 6 + src/lib/server/oidc/claims.ts | 45 + .../openid-configuration/+server.ts | 3 +- src/routes/admin/users/[id]/+page.server.ts | 11 + src/routes/admin/users/[id]/+page.svelte | 45 + src/routes/oidc/token/+server.ts | 5 + src/routes/oidc/userinfo/+server.ts | 6 + 22 files changed, 16002 insertions(+), 1 deletion(-) create mode 100644 drizzle/0020_cool_firebrand.sql create mode 100644 drizzle/meta/0020_snapshot.json create mode 100644 drizzle/mysql/0003_shiny_abomination.sql create mode 100644 drizzle/mysql/meta/0003_snapshot.json create mode 100644 drizzle/pg/0003_charming_the_fury.sql create mode 100644 drizzle/pg/meta/0003_snapshot.json create mode 100644 drizzle/sqlite/0003_white_speed_demon.sql create mode 100644 drizzle/sqlite/meta/0003_snapshot.json create mode 100644 src/lib/server/oidc/claims.ts diff --git a/drizzle/0020_cool_firebrand.sql b/drizzle/0020_cool_firebrand.sql new file mode 100644 index 0000000..d76083f --- /dev/null +++ b/drizzle/0020_cool_firebrand.sql @@ -0,0 +1,5 @@ +ALTER TABLE `users` ADD `address_street` text;--> statement-breakpoint +ALTER TABLE `users` ADD `address_locality` text;--> statement-breakpoint +ALTER TABLE `users` ADD `address_region` text;--> statement-breakpoint +ALTER TABLE `users` ADD `address_postal_code` text;--> statement-breakpoint +ALTER TABLE `users` ADD `address_country` text; \ No newline at end of file diff --git a/drizzle/meta/0020_snapshot.json b/drizzle/meta/0020_snapshot.json new file mode 100644 index 0000000..0c1d9e6 --- /dev/null +++ b/drizzle/meta/0020_snapshot.json @@ -0,0 +1,3793 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "cd086427-88fb-49fb-bab5-18d7806613e0", + "prevId": "9616e1e4-0bfa-4d36-ae26-a78b442ab1bf", + "tables": { + "audit_events": { + "name": "audit_events", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sp_or_client_id": { + "name": "sp_or_client_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ip": { + "name": "ip", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "detail_json": { + "name": "detail_json", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "hash": { + "name": "hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + } + }, + "indexes": { + "audit_events_tenant_kind_idx": { + "name": "audit_events_tenant_kind_idx", + "columns": [ + "tenant_id", + "kind" + ], + "isUnique": false + }, + "audit_events_tenant_created_idx": { + "name": "audit_events_tenant_created_idx", + "columns": [ + "tenant_id", + "created_at" + ], + "isUnique": false + }, + "audit_events_user_idx": { + "name": "audit_events_user_idx", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "audit_events_tenant_id_tenants_id_fk": { + "name": "audit_events_tenant_id_tenants_id_fk", + "tableFrom": "audit_events", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "audit_events_user_id_users_id_fk": { + "name": "audit_events_user_id_users_id_fk", + "tableFrom": "audit_events", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "client_skins": { + "name": "client_skins", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_type": { + "name": "client_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_ref_id": { + "name": "client_ref_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "skin_type": { + "name": "skin_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'login'" + }, + "fetch_url": { + "name": "fetch_url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "fetch_secret": { + "name": "fetch_secret", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cache_ttl_seconds": { + "name": "cache_ttl_seconds", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 3600 + }, + "enabled": { + "name": "enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "client_skins_unique": { + "name": "client_skins_unique", + "columns": [ + "tenant_id", + "client_type", + "client_ref_id", + "skin_type" + ], + "isUnique": true + } + }, + "foreignKeys": { + "client_skins_tenant_id_tenants_id_fk": { + "name": "client_skins_tenant_id_tenants_id_fk", + "tableFrom": "client_skins", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "credentials": { + "name": "credentials", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "secret": { + "name": "secret", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "credential_id": { + "name": "credential_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "counter": { + "name": "counter", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "transports": { + "name": "transports", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "totp_owner_id": { + "name": "totp_owner_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "used_at": { + "name": "used_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + } + }, + "indexes": { + "credentials_user_idx": { + "name": "credentials_user_idx", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "credentials_user_type_idx": { + "name": "credentials_user_type_idx", + "columns": [ + "user_id", + "type" + ], + "isUnique": false + }, + "credentials_webauthn_credential_id_uidx": { + "name": "credentials_webauthn_credential_id_uidx", + "columns": [ + "credential_id" + ], + "isUnique": true + }, + "credentials_totp_owner_uidx": { + "name": "credentials_totp_owner_uidx", + "columns": [ + "totp_owner_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "credentials_user_id_users_id_fk": { + "name": "credentials_user_id_users_id_fk", + "tableFrom": "credentials", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "departments": { + "name": "departments", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "parent_id": { + "name": "parent_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "manager_id": { + "name": "manager_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "display_order": { + "name": "display_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'active'" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + } + }, + "indexes": { + "departments_tenant_idx": { + "name": "departments_tenant_idx", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "departments_parent_idx": { + "name": "departments_parent_idx", + "columns": [ + "parent_id" + ], + "isUnique": false + }, + "departments_tenant_code_uidx": { + "name": "departments_tenant_code_uidx", + "columns": [ + "tenant_id", + "code" + ], + "isUnique": true + } + }, + "foreignKeys": { + "departments_tenant_id_tenants_id_fk": { + "name": "departments_tenant_id_tenants_id_fk", + "tableFrom": "departments", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "departments_parent_id_departments_id_fk": { + "name": "departments_parent_id_departments_id_fk", + "tableFrom": "departments", + "tableTo": "departments", + "columnsFrom": [ + "parent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "departments_manager_id_users_id_fk": { + "name": "departments_manager_id_users_id_fk", + "tableFrom": "departments", + "tableTo": "users", + "columnsFrom": [ + "manager_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "identities": { + "name": "identities", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "raw_profile_json": { + "name": "raw_profile_json", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "linked_at": { + "name": "linked_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + }, + "last_login_at": { + "name": "last_login_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "identities_tenant_provider_subject_uidx": { + "name": "identities_tenant_provider_subject_uidx", + "columns": [ + "tenant_id", + "provider", + "subject" + ], + "isUnique": true + }, + "identities_user_idx": { + "name": "identities_user_idx", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "identities_tenant_id_tenants_id_fk": { + "name": "identities_tenant_id_tenants_id_fk", + "tableFrom": "identities", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "identities_user_id_users_id_fk": { + "name": "identities_user_id_users_id_fk", + "tableFrom": "identities", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "identity_providers": { + "name": "identity_providers", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "client_secret_enc": { + "name": "client_secret_enc", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "discovery_url": { + "name": "discovery_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "metadata_xml": { + "name": "metadata_xml", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scopes": { + "name": "scopes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "config_json": { + "name": "config_json", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enabled": { + "name": "enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + } + }, + "indexes": { + "idp_tenant_idx": { + "name": "idp_tenant_idx", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "idp_tenant_name_uidx": { + "name": "idp_tenant_name_uidx", + "columns": [ + "tenant_id", + "name" + ], + "isUnique": true + } + }, + "foreignKeys": { + "identity_providers_tenant_id_tenants_id_fk": { + "name": "identity_providers_tenant_id_tenants_id_fk", + "tableFrom": "identity_providers", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "oidc_clients": { + "name": "oidc_clients", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_secret_hash": { + "name": "client_secret_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "redirect_uris": { + "name": "redirect_uris", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "post_logout_redirect_uris": { + "name": "post_logout_redirect_uris", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "frontchannel_logout_uri": { + "name": "frontchannel_logout_uri", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "frontchannel_logout_session_required": { + "name": "frontchannel_logout_session_required", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "backchannel_logout_uri": { + "name": "backchannel_logout_uri", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "backchannel_logout_session_required": { + "name": "backchannel_logout_session_required", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "scopes": { + "name": "scopes", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'openid'" + }, + "grant_types": { + "name": "grant_types", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'authorization_code,refresh_token'" + }, + "response_types": { + "name": "response_types", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'code'" + }, + "token_endpoint_auth_method": { + "name": "token_endpoint_auth_method", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'client_secret_basic'" + }, + "require_pkce": { + "name": "require_pkce", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "allow_wildcard_redirect_uri": { + "name": "allow_wildcard_redirect_uri", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "id_token_signed_response_alg": { + "name": "id_token_signed_response_alg", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'RS256'" + }, + "jwks_uri": { + "name": "jwks_uri", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "jwks": { + "name": "jwks", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enabled": { + "name": "enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + } + }, + "indexes": { + "oidc_clients_tenant_client_id_uidx": { + "name": "oidc_clients_tenant_client_id_uidx", + "columns": [ + "tenant_id", + "client_id" + ], + "isUnique": true + }, + "oidc_clients_tenant_idx": { + "name": "oidc_clients_tenant_idx", + "columns": [ + "tenant_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "oidc_clients_tenant_id_tenants_id_fk": { + "name": "oidc_clients_tenant_id_tenants_id_fk", + "tableFrom": "oidc_clients", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "oidc_grants": { + "name": "oidc_grants", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "code_hash": { + "name": "code_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "code_challenge": { + "name": "code_challenge", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "code_challenge_method": { + "name": "code_challenge_method", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "redirect_uri": { + "name": "redirect_uri", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "nonce": { + "name": "nonce", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "acr": { + "name": "acr", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "used_at": { + "name": "used_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + } + }, + "indexes": { + "oidc_grants_code_uidx": { + "name": "oidc_grants_code_uidx", + "columns": [ + "code" + ], + "isUnique": true + }, + "oidc_grants_code_hash_uidx": { + "name": "oidc_grants_code_hash_uidx", + "columns": [ + "code_hash" + ], + "isUnique": true + }, + "oidc_grants_tenant_client_idx": { + "name": "oidc_grants_tenant_client_idx", + "columns": [ + "tenant_id", + "client_id" + ], + "isUnique": false + }, + "oidc_grants_expires_idx": { + "name": "oidc_grants_expires_idx", + "columns": [ + "expires_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "oidc_grants_tenant_id_tenants_id_fk": { + "name": "oidc_grants_tenant_id_tenants_id_fk", + "tableFrom": "oidc_grants", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oidc_grants_user_id_users_id_fk": { + "name": "oidc_grants_user_id_users_id_fk", + "tableFrom": "oidc_grants", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oidc_grants_session_id_sessions_id_fk": { + "name": "oidc_grants_session_id_sessions_id_fk", + "tableFrom": "oidc_grants", + "tableTo": "sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "oidc_refresh_tokens": { + "name": "oidc_refresh_tokens", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "replaced_by_id": { + "name": "replaced_by_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + } + }, + "indexes": { + "oidc_refresh_tokens_hash_uidx": { + "name": "oidc_refresh_tokens_hash_uidx", + "columns": [ + "token_hash" + ], + "isUnique": true + }, + "oidc_refresh_tokens_user_idx": { + "name": "oidc_refresh_tokens_user_idx", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "oidc_refresh_tokens_tenant_id_tenants_id_fk": { + "name": "oidc_refresh_tokens_tenant_id_tenants_id_fk", + "tableFrom": "oidc_refresh_tokens", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oidc_refresh_tokens_user_id_users_id_fk": { + "name": "oidc_refresh_tokens_user_id_users_id_fk", + "tableFrom": "oidc_refresh_tokens", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oidc_refresh_tokens_session_id_sessions_id_fk": { + "name": "oidc_refresh_tokens_session_id_sessions_id_fk", + "tableFrom": "oidc_refresh_tokens", + "tableTo": "sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "parts": { + "name": "parts", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "team_id": { + "name": "team_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "leader_id": { + "name": "leader_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'active'" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + } + }, + "indexes": { + "parts_tenant_idx": { + "name": "parts_tenant_idx", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "parts_team_idx": { + "name": "parts_team_idx", + "columns": [ + "team_id" + ], + "isUnique": false + }, + "parts_tenant_code_uidx": { + "name": "parts_tenant_code_uidx", + "columns": [ + "tenant_id", + "code" + ], + "isUnique": true + } + }, + "foreignKeys": { + "parts_tenant_id_tenants_id_fk": { + "name": "parts_tenant_id_tenants_id_fk", + "tableFrom": "parts", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "parts_team_id_teams_id_fk": { + "name": "parts_team_id_teams_id_fk", + "tableFrom": "parts", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "parts_leader_id_users_id_fk": { + "name": "parts_leader_id_users_id_fk", + "tableFrom": "parts", + "tableTo": "users", + "columnsFrom": [ + "leader_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "password_reset_tokens": { + "name": "password_reset_tokens", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "used_at": { + "name": "used_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + } + }, + "indexes": { + "password_reset_tokens_user_idx": { + "name": "password_reset_tokens_user_idx", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "password_reset_tokens_hash_uidx": { + "name": "password_reset_tokens_hash_uidx", + "columns": [ + "token_hash" + ], + "isUnique": true + } + }, + "foreignKeys": { + "password_reset_tokens_user_id_users_id_fk": { + "name": "password_reset_tokens_user_id_users_id_fk", + "tableFrom": "password_reset_tokens", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "positions": { + "name": "positions", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "level": { + "name": "level", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + } + }, + "indexes": { + "positions_tenant_idx": { + "name": "positions_tenant_idx", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "positions_tenant_code_uidx": { + "name": "positions_tenant_code_uidx", + "columns": [ + "tenant_id", + "code" + ], + "isUnique": true + } + }, + "foreignKeys": { + "positions_tenant_id_tenants_id_fk": { + "name": "positions_tenant_id_tenants_id_fk", + "tableFrom": "positions", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "rate_limits": { + "name": "rate_limits", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "count": { + "name": "count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "saml_authn_request_ids": { + "name": "saml_authn_request_ids", + "columns": { + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "request_id": { + "name": "request_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sp_entity_id": { + "name": "sp_entity_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "seen_at": { + "name": "seen_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "saml_authn_request_ids_tenant_req_uidx": { + "name": "saml_authn_request_ids_tenant_req_uidx", + "columns": [ + "tenant_id", + "request_id" + ], + "isUnique": true + }, + "saml_authn_request_ids_expires_idx": { + "name": "saml_authn_request_ids_expires_idx", + "columns": [ + "expires_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "saml_authn_request_ids_tenant_id_tenants_id_fk": { + "name": "saml_authn_request_ids_tenant_id_tenants_id_fk", + "tableFrom": "saml_authn_request_ids", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "saml_sessions": { + "name": "saml_sessions", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sp_id": { + "name": "sp_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "session_index": { + "name": "session_index", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name_id": { + "name": "name_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name_id_format": { + "name": "name_id_format", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "not_on_or_after": { + "name": "not_on_or_after", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + }, + "ended_at": { + "name": "ended_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "saml_sessions_session_index_uidx": { + "name": "saml_sessions_session_index_uidx", + "columns": [ + "session_index" + ], + "isUnique": true + }, + "saml_sessions_tenant_sp_idx": { + "name": "saml_sessions_tenant_sp_idx", + "columns": [ + "tenant_id", + "sp_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "saml_sessions_tenant_id_tenants_id_fk": { + "name": "saml_sessions_tenant_id_tenants_id_fk", + "tableFrom": "saml_sessions", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "saml_sessions_sp_id_saml_sps_id_fk": { + "name": "saml_sessions_sp_id_saml_sps_id_fk", + "tableFrom": "saml_sessions", + "tableTo": "saml_sps", + "columnsFrom": [ + "sp_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "saml_sessions_user_id_users_id_fk": { + "name": "saml_sessions_user_id_users_id_fk", + "tableFrom": "saml_sessions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "saml_sessions_session_id_sessions_id_fk": { + "name": "saml_sessions_session_id_sessions_id_fk", + "tableFrom": "saml_sessions", + "tableTo": "sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "saml_slo_states": { + "name": "saml_slo_states", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "idp_session_record_id": { + "name": "idp_session_record_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "initiating_sp_entity_id": { + "name": "initiating_sp_entity_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "in_response_to": { + "name": "in_response_to", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "initiator_slo_url": { + "name": "initiator_slo_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "completion_uri": { + "name": "completion_uri", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "pending_sp_data_json": { + "name": "pending_sp_data_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "saml_slo_states_tenant_id_tenants_id_fk": { + "name": "saml_slo_states_tenant_id_tenants_id_fk", + "tableFrom": "saml_slo_states", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "saml_slo_states_user_id_users_id_fk": { + "name": "saml_slo_states_user_id_users_id_fk", + "tableFrom": "saml_slo_states", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "saml_sps": { + "name": "saml_sps", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "entity_id": { + "name": "entity_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "acs_url": { + "name": "acs_url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "acs_binding": { + "name": "acs_binding", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST'" + }, + "slo_url": { + "name": "slo_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "slo_binding": { + "name": "slo_binding", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cert": { + "name": "cert", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name_id_format": { + "name": "name_id_format", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress'" + }, + "sign_assertion": { + "name": "sign_assertion", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "sign_response": { + "name": "sign_response", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "encrypt_assertion": { + "name": "encrypt_assertion", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "want_authn_requests_signed": { + "name": "want_authn_requests_signed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "attribute_mapping_json": { + "name": "attribute_mapping_json", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "allowed_attributes": { + "name": "allowed_attributes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enabled": { + "name": "enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + } + }, + "indexes": { + "saml_sps_tenant_entity_id_uidx": { + "name": "saml_sps_tenant_entity_id_uidx", + "columns": [ + "tenant_id", + "entity_id" + ], + "isUnique": true + }, + "saml_sps_tenant_idx": { + "name": "saml_sps_tenant_idx", + "columns": [ + "tenant_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "saml_sps_tenant_id_tenants_id_fk": { + "name": "saml_sps_tenant_id_tenants_id_fk", + "tableFrom": "saml_sps", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "service_roles": { + "name": "service_roles", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "service_type": { + "name": "service_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "service_ref_id": { + "name": "service_ref_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_default": { + "name": "is_default", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "display_order": { + "name": "display_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + } + }, + "indexes": { + "service_roles_service_key_uidx": { + "name": "service_roles_service_key_uidx", + "columns": [ + "service_type", + "service_ref_id", + "key" + ], + "isUnique": true + }, + "service_roles_tenant_service_idx": { + "name": "service_roles_tenant_service_idx", + "columns": [ + "tenant_id", + "service_type", + "service_ref_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "service_roles_tenant_id_tenants_id_fk": { + "name": "service_roles_tenant_id_tenants_id_fk", + "tableFrom": "service_roles", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "sessions": { + "name": "sessions", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "idp_session_id": { + "name": "idp_session_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "amr": { + "name": "amr", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "acr": { + "name": "acr", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ip": { + "name": "ip", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "sessions_idp_session_id_uidx": { + "name": "sessions_idp_session_id_uidx", + "columns": [ + "idp_session_id" + ], + "isUnique": true + }, + "sessions_user_idx": { + "name": "sessions_user_idx", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "sessions_expires_idx": { + "name": "sessions_expires_idx", + "columns": [ + "expires_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "sessions_tenant_id_tenants_id_fk": { + "name": "sessions_tenant_id_tenants_id_fk", + "tableFrom": "sessions", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "sessions_user_id_users_id_fk": { + "name": "sessions_user_id_users_id_fk", + "tableFrom": "sessions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "signing_keys": { + "name": "signing_keys", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "kid": { + "name": "kid", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "use": { + "name": "use", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'sig'" + }, + "alg": { + "name": "alg", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "public_jwk": { + "name": "public_jwk", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "private_jwk_encrypted": { + "name": "private_jwk_encrypted", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "cert_pem": { + "name": "cert_pem", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "active": { + "name": "active", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + }, + "rotated_at": { + "name": "rotated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "not_after": { + "name": "not_after", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "signing_keys_tenant_kid_uidx": { + "name": "signing_keys_tenant_kid_uidx", + "columns": [ + "tenant_id", + "kid" + ], + "isUnique": true + }, + "signing_keys_tenant_active_idx": { + "name": "signing_keys_tenant_active_idx", + "columns": [ + "tenant_id", + "active" + ], + "isUnique": false + }, + "signing_keys_tenant_one_active_uidx": { + "name": "signing_keys_tenant_one_active_uidx", + "columns": [ + "tenant_id" + ], + "isUnique": true, + "where": "active = 1" + } + }, + "foreignKeys": { + "signing_keys_tenant_id_tenants_id_fk": { + "name": "signing_keys_tenant_id_tenants_id_fk", + "tableFrom": "signing_keys", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "teams": { + "name": "teams", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "department_id": { + "name": "department_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "leader_id": { + "name": "leader_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'active'" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + } + }, + "indexes": { + "teams_tenant_idx": { + "name": "teams_tenant_idx", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "teams_department_idx": { + "name": "teams_department_idx", + "columns": [ + "department_id" + ], + "isUnique": false + }, + "teams_tenant_code_uidx": { + "name": "teams_tenant_code_uidx", + "columns": [ + "tenant_id", + "code" + ], + "isUnique": true + } + }, + "foreignKeys": { + "teams_tenant_id_tenants_id_fk": { + "name": "teams_tenant_id_tenants_id_fk", + "tableFrom": "teams", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "teams_department_id_departments_id_fk": { + "name": "teams_department_id_departments_id_fk", + "tableFrom": "teams", + "tableTo": "departments", + "columnsFrom": [ + "department_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "teams_leader_id_users_id_fk": { + "name": "teams_leader_id_users_id_fk", + "tableFrom": "teams", + "tableTo": "users", + "columnsFrom": [ + "leader_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tenants": { + "name": "tenants", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'active'" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + } + }, + "indexes": { + "tenants_slug_uidx": { + "name": "tenants_slug_uidx", + "columns": [ + "slug" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user_departments": { + "name": "user_departments", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "department_id": { + "name": "department_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "position_id": { + "name": "position_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "job_title": { + "name": "job_title", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_primary": { + "name": "is_primary", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "started_at": { + "name": "started_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + }, + "ended_at": { + "name": "ended_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + } + }, + "indexes": { + "user_departments_user_idx": { + "name": "user_departments_user_idx", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "user_departments_dept_idx": { + "name": "user_departments_dept_idx", + "columns": [ + "department_id" + ], + "isUnique": false + }, + "user_departments_tenant_idx": { + "name": "user_departments_tenant_idx", + "columns": [ + "tenant_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "user_departments_tenant_id_tenants_id_fk": { + "name": "user_departments_tenant_id_tenants_id_fk", + "tableFrom": "user_departments", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_departments_user_id_users_id_fk": { + "name": "user_departments_user_id_users_id_fk", + "tableFrom": "user_departments", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_departments_department_id_departments_id_fk": { + "name": "user_departments_department_id_departments_id_fk", + "tableFrom": "user_departments", + "tableTo": "departments", + "columnsFrom": [ + "department_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_departments_position_id_positions_id_fk": { + "name": "user_departments_position_id_positions_id_fk", + "tableFrom": "user_departments", + "tableTo": "positions", + "columnsFrom": [ + "position_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user_parts": { + "name": "user_parts", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "part_id": { + "name": "part_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "job_title": { + "name": "job_title", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_primary": { + "name": "is_primary", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "started_at": { + "name": "started_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + }, + "ended_at": { + "name": "ended_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + } + }, + "indexes": { + "user_parts_user_idx": { + "name": "user_parts_user_idx", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "user_parts_part_idx": { + "name": "user_parts_part_idx", + "columns": [ + "part_id" + ], + "isUnique": false + }, + "user_parts_tenant_idx": { + "name": "user_parts_tenant_idx", + "columns": [ + "tenant_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "user_parts_tenant_id_tenants_id_fk": { + "name": "user_parts_tenant_id_tenants_id_fk", + "tableFrom": "user_parts", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_parts_user_id_users_id_fk": { + "name": "user_parts_user_id_users_id_fk", + "tableFrom": "user_parts", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_parts_part_id_parts_id_fk": { + "name": "user_parts_part_id_parts_id_fk", + "tableFrom": "user_parts", + "tableTo": "parts", + "columnsFrom": [ + "part_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user_service_assignments": { + "name": "user_service_assignments", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "service_type": { + "name": "service_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "service_ref_id": { + "name": "service_ref_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "service_role_id": { + "name": "service_role_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "attributes_json": { + "name": "attributes_json", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "granted_by": { + "name": "granted_by", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "granted_at": { + "name": "granted_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + } + }, + "indexes": { + "user_service_assignments_user_service_uidx": { + "name": "user_service_assignments_user_service_uidx", + "columns": [ + "tenant_id", + "user_id", + "service_type", + "service_ref_id" + ], + "isUnique": true + }, + "user_service_assignments_tenant_user_idx": { + "name": "user_service_assignments_tenant_user_idx", + "columns": [ + "tenant_id", + "user_id" + ], + "isUnique": false + }, + "user_service_assignments_tenant_service_idx": { + "name": "user_service_assignments_tenant_service_idx", + "columns": [ + "tenant_id", + "service_type", + "service_ref_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "user_service_assignments_tenant_id_tenants_id_fk": { + "name": "user_service_assignments_tenant_id_tenants_id_fk", + "tableFrom": "user_service_assignments", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_service_assignments_user_id_users_id_fk": { + "name": "user_service_assignments_user_id_users_id_fk", + "tableFrom": "user_service_assignments", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_service_assignments_service_role_id_service_roles_id_fk": { + "name": "user_service_assignments_service_role_id_service_roles_id_fk", + "tableFrom": "user_service_assignments", + "tableTo": "service_roles", + "columnsFrom": [ + "service_role_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user_teams": { + "name": "user_teams", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "team_id": { + "name": "team_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "job_title": { + "name": "job_title", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_primary": { + "name": "is_primary", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "started_at": { + "name": "started_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + }, + "ended_at": { + "name": "ended_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + } + }, + "indexes": { + "user_teams_user_idx": { + "name": "user_teams_user_idx", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "user_teams_team_idx": { + "name": "user_teams_team_idx", + "columns": [ + "team_id" + ], + "isUnique": false + }, + "user_teams_tenant_idx": { + "name": "user_teams_tenant_idx", + "columns": [ + "tenant_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "user_teams_tenant_id_tenants_id_fk": { + "name": "user_teams_tenant_id_tenants_id_fk", + "tableFrom": "user_teams", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_teams_user_id_users_id_fk": { + "name": "user_teams_user_id_users_id_fk", + "tableFrom": "user_teams", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_teams_team_id_teams_id_fk": { + "name": "user_teams_team_id_teams_id_fk", + "tableFrom": "user_teams", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "users": { + "name": "users", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email_verified_at": { + "name": "email_verified_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'user'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'active'" + }, + "given_name": { + "name": "given_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "family_name": { + "name": "family_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "phone_number": { + "name": "phone_number", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "phone_verified_at": { + "name": "phone_verified_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "avatar_url": { + "name": "avatar_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "locale": { + "name": "locale", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'ko-KR'" + }, + "zoneinfo": { + "name": "zoneinfo", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'Asia/Seoul'" + }, + "bio": { + "name": "bio", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "birthdate": { + "name": "birthdate", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "address_street": { + "name": "address_street", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "address_locality": { + "name": "address_locality", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "address_region": { + "name": "address_region", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "address_postal_code": { + "name": "address_postal_code", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "address_country": { + "name": "address_country", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + } + }, + "indexes": { + "users_tenant_email_uidx": { + "name": "users_tenant_email_uidx", + "columns": [ + "tenant_id", + "email" + ], + "isUnique": true + }, + "users_tenant_username_uidx": { + "name": "users_tenant_username_uidx", + "columns": [ + "tenant_id", + "username" + ], + "isUnique": true + }, + "users_tenant_idx": { + "name": "users_tenant_idx", + "columns": [ + "tenant_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "users_tenant_id_tenants_id_fk": { + "name": "users_tenant_id_tenants_id_fk", + "tableFrom": "users", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "webauthn_challenges": { + "name": "webauthn_challenges", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "challenge": { + "name": "challenge", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "used_at": { + "name": "used_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "webauthn_challenges_tenant_challenge_uidx": { + "name": "webauthn_challenges_tenant_challenge_uidx", + "columns": [ + "tenant_id", + "challenge" + ], + "isUnique": true + }, + "webauthn_challenges_tenant_expires_idx": { + "name": "webauthn_challenges_tenant_expires_idx", + "columns": [ + "tenant_id", + "expires_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "webauthn_challenges_tenant_id_tenants_id_fk": { + "name": "webauthn_challenges_tenant_id_tenants_id_fk", + "tableFrom": "webauthn_challenges", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "webauthn_challenges_user_id_users_id_fk": { + "name": "webauthn_challenges_user_id_users_id_fk", + "tableFrom": "webauthn_challenges", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index c8099d9..9ab53be 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -141,6 +141,13 @@ "when": 1783222881745, "tag": "0019_regular_screwball", "breakpoints": true + }, + { + "idx": 20, + "version": "6", + "when": 1783246066470, + "tag": "0020_cool_firebrand", + "breakpoints": true } ] } \ No newline at end of file diff --git a/drizzle/mysql/0003_shiny_abomination.sql b/drizzle/mysql/0003_shiny_abomination.sql new file mode 100644 index 0000000..d76083f --- /dev/null +++ b/drizzle/mysql/0003_shiny_abomination.sql @@ -0,0 +1,5 @@ +ALTER TABLE `users` ADD `address_street` text;--> statement-breakpoint +ALTER TABLE `users` ADD `address_locality` text;--> statement-breakpoint +ALTER TABLE `users` ADD `address_region` text;--> statement-breakpoint +ALTER TABLE `users` ADD `address_postal_code` text;--> statement-breakpoint +ALTER TABLE `users` ADD `address_country` text; \ No newline at end of file diff --git a/drizzle/mysql/meta/0003_snapshot.json b/drizzle/mysql/meta/0003_snapshot.json new file mode 100644 index 0000000..dfda75b --- /dev/null +++ b/drizzle/mysql/meta/0003_snapshot.json @@ -0,0 +1,3974 @@ +{ + "version": "5", + "dialect": "mysql", + "id": "373a9ae6-201b-430a-8654-ee3e337f697c", + "prevId": "76c82e46-d503-4cfc-9bf3-8ffa9395c9b7", + "tables": { + "audit_events": { + "name": "audit_events", + "columns": { + "id": { + "name": "id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sp_or_client_id": { + "name": "sp_or_client_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "outcome": { + "name": "outcome", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ip": { + "name": "ip", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "detail_json": { + "name": "detail_json", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "hash": { + "name": "hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "datetime(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP(3))" + } + }, + "indexes": { + "audit_events_tenant_kind_idx": { + "name": "audit_events_tenant_kind_idx", + "columns": [ + "tenant_id", + "kind" + ], + "isUnique": false + }, + "audit_events_tenant_created_idx": { + "name": "audit_events_tenant_created_idx", + "columns": [ + "tenant_id", + "created_at" + ], + "isUnique": false + }, + "audit_events_user_idx": { + "name": "audit_events_user_idx", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "audit_events_tenant_id_tenants_id_fk": { + "name": "audit_events_tenant_id_tenants_id_fk", + "tableFrom": "audit_events", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "audit_events_user_id_users_id_fk": { + "name": "audit_events_user_id_users_id_fk", + "tableFrom": "audit_events", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "audit_events_id": { + "name": "audit_events_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "client_skins": { + "name": "client_skins", + "columns": { + "id": { + "name": "id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_type": { + "name": "client_type", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_ref_id": { + "name": "client_ref_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "skin_type": { + "name": "skin_type", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'login'" + }, + "fetch_url": { + "name": "fetch_url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "fetch_secret": { + "name": "fetch_secret", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cache_ttl_seconds": { + "name": "cache_ttl_seconds", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 3600 + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "datetime(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "client_skins_unique": { + "name": "client_skins_unique", + "columns": [ + "tenant_id", + "client_type", + "client_ref_id", + "skin_type" + ], + "isUnique": true + } + }, + "foreignKeys": { + "client_skins_tenant_id_tenants_id_fk": { + "name": "client_skins_tenant_id_tenants_id_fk", + "tableFrom": "client_skins", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "client_skins_id": { + "name": "client_skins_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "credentials": { + "name": "credentials", + "columns": { + "id": { + "name": "id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "secret": { + "name": "secret", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "credential_id": { + "name": "credential_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "counter": { + "name": "counter", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "transports": { + "name": "transports", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "totp_owner_id": { + "name": "totp_owner_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "datetime(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "used_at": { + "name": "used_at", + "type": "datetime(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "datetime(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP(3))" + } + }, + "indexes": { + "credentials_user_idx": { + "name": "credentials_user_idx", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "credentials_user_type_idx": { + "name": "credentials_user_type_idx", + "columns": [ + "user_id", + "type" + ], + "isUnique": false + }, + "credentials_webauthn_credential_id_uidx": { + "name": "credentials_webauthn_credential_id_uidx", + "columns": [ + "credential_id" + ], + "isUnique": true + }, + "credentials_totp_owner_uidx": { + "name": "credentials_totp_owner_uidx", + "columns": [ + "totp_owner_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "credentials_user_id_users_id_fk": { + "name": "credentials_user_id_users_id_fk", + "tableFrom": "credentials", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "credentials_id": { + "name": "credentials_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "departments": { + "name": "departments", + "columns": { + "id": { + "name": "id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "parent_id": { + "name": "parent_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "code": { + "name": "code", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "manager_id": { + "name": "manager_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "display_order": { + "name": "display_order", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "status": { + "name": "status", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'active'" + }, + "created_at": { + "name": "created_at", + "type": "datetime(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP(3))" + }, + "updated_at": { + "name": "updated_at", + "type": "datetime(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP(3))" + } + }, + "indexes": { + "departments_tenant_idx": { + "name": "departments_tenant_idx", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "departments_parent_idx": { + "name": "departments_parent_idx", + "columns": [ + "parent_id" + ], + "isUnique": false + }, + "departments_tenant_code_uidx": { + "name": "departments_tenant_code_uidx", + "columns": [ + "tenant_id", + "code" + ], + "isUnique": true + } + }, + "foreignKeys": { + "departments_tenant_id_tenants_id_fk": { + "name": "departments_tenant_id_tenants_id_fk", + "tableFrom": "departments", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "departments_parent_id_departments_id_fk": { + "name": "departments_parent_id_departments_id_fk", + "tableFrom": "departments", + "tableTo": "departments", + "columnsFrom": [ + "parent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "departments_manager_id_users_id_fk": { + "name": "departments_manager_id_users_id_fk", + "tableFrom": "departments", + "tableTo": "users", + "columnsFrom": [ + "manager_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "departments_id": { + "name": "departments_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "identities": { + "name": "identities", + "columns": { + "id": { + "name": "id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "subject": { + "name": "subject", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "raw_profile_json": { + "name": "raw_profile_json", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "linked_at": { + "name": "linked_at", + "type": "datetime(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP(3))" + }, + "last_login_at": { + "name": "last_login_at", + "type": "datetime(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "identities_tenant_provider_subject_uidx": { + "name": "identities_tenant_provider_subject_uidx", + "columns": [ + "tenant_id", + "provider", + "subject" + ], + "isUnique": true + }, + "identities_user_idx": { + "name": "identities_user_idx", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "identities_tenant_id_tenants_id_fk": { + "name": "identities_tenant_id_tenants_id_fk", + "tableFrom": "identities", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "identities_user_id_users_id_fk": { + "name": "identities_user_id_users_id_fk", + "tableFrom": "identities", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "identities_id": { + "name": "identities_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "identity_providers": { + "name": "identity_providers", + "columns": { + "id": { + "name": "id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "client_secret_enc": { + "name": "client_secret_enc", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "discovery_url": { + "name": "discovery_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "metadata_xml": { + "name": "metadata_xml", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scopes": { + "name": "scopes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "config_json": { + "name": "config_json", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "datetime(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP(3))" + }, + "updated_at": { + "name": "updated_at", + "type": "datetime(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP(3))" + } + }, + "indexes": { + "idp_tenant_idx": { + "name": "idp_tenant_idx", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "idp_tenant_name_uidx": { + "name": "idp_tenant_name_uidx", + "columns": [ + "tenant_id", + "name" + ], + "isUnique": true + } + }, + "foreignKeys": { + "identity_providers_tenant_id_tenants_id_fk": { + "name": "identity_providers_tenant_id_tenants_id_fk", + "tableFrom": "identity_providers", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "identity_providers_id": { + "name": "identity_providers_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "oidc_clients": { + "name": "oidc_clients", + "columns": { + "id": { + "name": "id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_secret_hash": { + "name": "client_secret_hash", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "redirect_uris": { + "name": "redirect_uris", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "post_logout_redirect_uris": { + "name": "post_logout_redirect_uris", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "frontchannel_logout_uri": { + "name": "frontchannel_logout_uri", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "frontchannel_logout_session_required": { + "name": "frontchannel_logout_session_required", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "backchannel_logout_uri": { + "name": "backchannel_logout_uri", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "backchannel_logout_session_required": { + "name": "backchannel_logout_session_required", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "scopes": { + "name": "scopes", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('openid')" + }, + "grant_types": { + "name": "grant_types", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('authorization_code,refresh_token')" + }, + "response_types": { + "name": "response_types", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('code')" + }, + "token_endpoint_auth_method": { + "name": "token_endpoint_auth_method", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'client_secret_basic'" + }, + "require_pkce": { + "name": "require_pkce", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "allow_wildcard_redirect_uri": { + "name": "allow_wildcard_redirect_uri", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "id_token_signed_response_alg": { + "name": "id_token_signed_response_alg", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('RS256')" + }, + "jwks_uri": { + "name": "jwks_uri", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "jwks": { + "name": "jwks", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "datetime(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP(3))" + }, + "updated_at": { + "name": "updated_at", + "type": "datetime(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP(3))" + } + }, + "indexes": { + "oidc_clients_tenant_client_id_uidx": { + "name": "oidc_clients_tenant_client_id_uidx", + "columns": [ + "tenant_id", + "client_id" + ], + "isUnique": true + }, + "oidc_clients_tenant_idx": { + "name": "oidc_clients_tenant_idx", + "columns": [ + "tenant_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "oidc_clients_tenant_id_tenants_id_fk": { + "name": "oidc_clients_tenant_id_tenants_id_fk", + "tableFrom": "oidc_clients", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "oidc_clients_id": { + "name": "oidc_clients_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "oidc_grants": { + "name": "oidc_grants", + "columns": { + "id": { + "name": "id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "session_id": { + "name": "session_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "code": { + "name": "code", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "code_hash": { + "name": "code_hash", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "code_challenge": { + "name": "code_challenge", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "code_challenge_method": { + "name": "code_challenge_method", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "redirect_uri": { + "name": "redirect_uri", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "nonce": { + "name": "nonce", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "acr": { + "name": "acr", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "datetime(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "used_at": { + "name": "used_at", + "type": "datetime(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "datetime(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP(3))" + } + }, + "indexes": { + "oidc_grants_code_uidx": { + "name": "oidc_grants_code_uidx", + "columns": [ + "code" + ], + "isUnique": true + }, + "oidc_grants_code_hash_uidx": { + "name": "oidc_grants_code_hash_uidx", + "columns": [ + "code_hash" + ], + "isUnique": true + }, + "oidc_grants_tenant_client_idx": { + "name": "oidc_grants_tenant_client_idx", + "columns": [ + "tenant_id", + "client_id" + ], + "isUnique": false + }, + "oidc_grants_expires_idx": { + "name": "oidc_grants_expires_idx", + "columns": [ + "expires_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "oidc_grants_tenant_id_tenants_id_fk": { + "name": "oidc_grants_tenant_id_tenants_id_fk", + "tableFrom": "oidc_grants", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oidc_grants_user_id_users_id_fk": { + "name": "oidc_grants_user_id_users_id_fk", + "tableFrom": "oidc_grants", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oidc_grants_session_id_sessions_id_fk": { + "name": "oidc_grants_session_id_sessions_id_fk", + "tableFrom": "oidc_grants", + "tableTo": "sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "oidc_grants_id": { + "name": "oidc_grants_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "oidc_refresh_tokens": { + "name": "oidc_refresh_tokens", + "columns": { + "id": { + "name": "id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "session_id": { + "name": "session_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "token_hash": { + "name": "token_hash", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "datetime(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "datetime(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "replaced_by_id": { + "name": "replaced_by_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "datetime(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP(3))" + } + }, + "indexes": { + "oidc_refresh_tokens_hash_uidx": { + "name": "oidc_refresh_tokens_hash_uidx", + "columns": [ + "token_hash" + ], + "isUnique": true + }, + "oidc_refresh_tokens_user_idx": { + "name": "oidc_refresh_tokens_user_idx", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "oidc_refresh_tokens_tenant_id_tenants_id_fk": { + "name": "oidc_refresh_tokens_tenant_id_tenants_id_fk", + "tableFrom": "oidc_refresh_tokens", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oidc_refresh_tokens_user_id_users_id_fk": { + "name": "oidc_refresh_tokens_user_id_users_id_fk", + "tableFrom": "oidc_refresh_tokens", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oidc_refresh_tokens_session_id_sessions_id_fk": { + "name": "oidc_refresh_tokens_session_id_sessions_id_fk", + "tableFrom": "oidc_refresh_tokens", + "tableTo": "sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "oidc_refresh_tokens_id": { + "name": "oidc_refresh_tokens_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "parts": { + "name": "parts", + "columns": { + "id": { + "name": "id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "team_id": { + "name": "team_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "code": { + "name": "code", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "leader_id": { + "name": "leader_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'active'" + }, + "created_at": { + "name": "created_at", + "type": "datetime(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP(3))" + }, + "updated_at": { + "name": "updated_at", + "type": "datetime(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP(3))" + } + }, + "indexes": { + "parts_tenant_idx": { + "name": "parts_tenant_idx", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "parts_team_idx": { + "name": "parts_team_idx", + "columns": [ + "team_id" + ], + "isUnique": false + }, + "parts_tenant_code_uidx": { + "name": "parts_tenant_code_uidx", + "columns": [ + "tenant_id", + "code" + ], + "isUnique": true + } + }, + "foreignKeys": { + "parts_tenant_id_tenants_id_fk": { + "name": "parts_tenant_id_tenants_id_fk", + "tableFrom": "parts", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "parts_team_id_teams_id_fk": { + "name": "parts_team_id_teams_id_fk", + "tableFrom": "parts", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "parts_leader_id_users_id_fk": { + "name": "parts_leader_id_users_id_fk", + "tableFrom": "parts", + "tableTo": "users", + "columnsFrom": [ + "leader_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "parts_id": { + "name": "parts_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "password_reset_tokens": { + "name": "password_reset_tokens", + "columns": { + "id": { + "name": "id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token_hash": { + "name": "token_hash", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "datetime(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "used_at": { + "name": "used_at", + "type": "datetime(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "datetime(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP(3))" + } + }, + "indexes": { + "password_reset_tokens_user_idx": { + "name": "password_reset_tokens_user_idx", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "password_reset_tokens_hash_uidx": { + "name": "password_reset_tokens_hash_uidx", + "columns": [ + "token_hash" + ], + "isUnique": true + } + }, + "foreignKeys": { + "password_reset_tokens_user_id_users_id_fk": { + "name": "password_reset_tokens_user_id_users_id_fk", + "tableFrom": "password_reset_tokens", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "password_reset_tokens_id": { + "name": "password_reset_tokens_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "positions": { + "name": "positions", + "columns": { + "id": { + "name": "id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "code": { + "name": "code", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "level": { + "name": "level", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "datetime(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP(3))" + }, + "updated_at": { + "name": "updated_at", + "type": "datetime(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP(3))" + } + }, + "indexes": { + "positions_tenant_idx": { + "name": "positions_tenant_idx", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "positions_tenant_code_uidx": { + "name": "positions_tenant_code_uidx", + "columns": [ + "tenant_id", + "code" + ], + "isUnique": true + } + }, + "foreignKeys": { + "positions_tenant_id_tenants_id_fk": { + "name": "positions_tenant_id_tenants_id_fk", + "tableFrom": "positions", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "positions_id": { + "name": "positions_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "rate_limits": { + "name": "rate_limits", + "columns": { + "key": { + "name": "key", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "count": { + "name": "count", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "expires_at": { + "name": "expires_at", + "type": "datetime(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "rate_limits_key": { + "name": "rate_limits_key", + "columns": [ + "key" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "saml_authn_request_ids": { + "name": "saml_authn_request_ids", + "columns": { + "tenant_id": { + "name": "tenant_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "request_id": { + "name": "request_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sp_entity_id": { + "name": "sp_entity_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "seen_at": { + "name": "seen_at", + "type": "datetime(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP(3))" + }, + "expires_at": { + "name": "expires_at", + "type": "datetime(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "saml_authn_request_ids_tenant_req_uidx": { + "name": "saml_authn_request_ids_tenant_req_uidx", + "columns": [ + "tenant_id", + "request_id" + ], + "isUnique": true + }, + "saml_authn_request_ids_expires_idx": { + "name": "saml_authn_request_ids_expires_idx", + "columns": [ + "expires_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "saml_authn_request_ids_tenant_id_tenants_id_fk": { + "name": "saml_authn_request_ids_tenant_id_tenants_id_fk", + "tableFrom": "saml_authn_request_ids", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "saml_sessions": { + "name": "saml_sessions", + "columns": { + "id": { + "name": "id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sp_id": { + "name": "sp_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "session_id": { + "name": "session_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "session_index": { + "name": "session_index", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name_id": { + "name": "name_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name_id_format": { + "name": "name_id_format", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "not_on_or_after": { + "name": "not_on_or_after", + "type": "datetime(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "datetime(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP(3))" + }, + "ended_at": { + "name": "ended_at", + "type": "datetime(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "saml_sessions_session_index_uidx": { + "name": "saml_sessions_session_index_uidx", + "columns": [ + "session_index" + ], + "isUnique": true + }, + "saml_sessions_tenant_sp_idx": { + "name": "saml_sessions_tenant_sp_idx", + "columns": [ + "tenant_id", + "sp_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "saml_sessions_tenant_id_tenants_id_fk": { + "name": "saml_sessions_tenant_id_tenants_id_fk", + "tableFrom": "saml_sessions", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "saml_sessions_sp_id_saml_sps_id_fk": { + "name": "saml_sessions_sp_id_saml_sps_id_fk", + "tableFrom": "saml_sessions", + "tableTo": "saml_sps", + "columnsFrom": [ + "sp_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "saml_sessions_user_id_users_id_fk": { + "name": "saml_sessions_user_id_users_id_fk", + "tableFrom": "saml_sessions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "saml_sessions_session_id_sessions_id_fk": { + "name": "saml_sessions_session_id_sessions_id_fk", + "tableFrom": "saml_sessions", + "tableTo": "sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "saml_sessions_id": { + "name": "saml_sessions_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "saml_slo_states": { + "name": "saml_slo_states", + "columns": { + "id": { + "name": "id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "idp_session_record_id": { + "name": "idp_session_record_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "initiating_sp_entity_id": { + "name": "initiating_sp_entity_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "in_response_to": { + "name": "in_response_to", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "initiator_slo_url": { + "name": "initiator_slo_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "completion_uri": { + "name": "completion_uri", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "pending_sp_data_json": { + "name": "pending_sp_data_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "datetime(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP(3))" + }, + "expires_at": { + "name": "expires_at", + "type": "datetime(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "saml_slo_states_tenant_id_tenants_id_fk": { + "name": "saml_slo_states_tenant_id_tenants_id_fk", + "tableFrom": "saml_slo_states", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "saml_slo_states_user_id_users_id_fk": { + "name": "saml_slo_states_user_id_users_id_fk", + "tableFrom": "saml_slo_states", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "saml_slo_states_id": { + "name": "saml_slo_states_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "saml_sps": { + "name": "saml_sps", + "columns": { + "id": { + "name": "id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "entity_id": { + "name": "entity_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "acs_url": { + "name": "acs_url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "acs_binding": { + "name": "acs_binding", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST'" + }, + "slo_url": { + "name": "slo_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "slo_binding": { + "name": "slo_binding", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cert": { + "name": "cert", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name_id_format": { + "name": "name_id_format", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress'" + }, + "sign_assertion": { + "name": "sign_assertion", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "sign_response": { + "name": "sign_response", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "encrypt_assertion": { + "name": "encrypt_assertion", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "want_authn_requests_signed": { + "name": "want_authn_requests_signed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "attribute_mapping_json": { + "name": "attribute_mapping_json", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "allowed_attributes": { + "name": "allowed_attributes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "datetime(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP(3))" + }, + "updated_at": { + "name": "updated_at", + "type": "datetime(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP(3))" + } + }, + "indexes": { + "saml_sps_tenant_entity_id_uidx": { + "name": "saml_sps_tenant_entity_id_uidx", + "columns": [ + "tenant_id", + "entity_id" + ], + "isUnique": true + }, + "saml_sps_tenant_idx": { + "name": "saml_sps_tenant_idx", + "columns": [ + "tenant_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "saml_sps_tenant_id_tenants_id_fk": { + "name": "saml_sps_tenant_id_tenants_id_fk", + "tableFrom": "saml_sps", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "saml_sps_id": { + "name": "saml_sps_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "service_roles": { + "name": "service_roles", + "columns": { + "id": { + "name": "id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "service_type": { + "name": "service_type", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "service_ref_id": { + "name": "service_ref_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "label": { + "name": "label", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "display_order": { + "name": "display_order", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "datetime(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP(3))" + }, + "updated_at": { + "name": "updated_at", + "type": "datetime(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP(3))" + } + }, + "indexes": { + "service_roles_service_key_uidx": { + "name": "service_roles_service_key_uidx", + "columns": [ + "service_type", + "service_ref_id", + "key" + ], + "isUnique": true + }, + "service_roles_tenant_service_idx": { + "name": "service_roles_tenant_service_idx", + "columns": [ + "tenant_id", + "service_type", + "service_ref_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "service_roles_tenant_id_tenants_id_fk": { + "name": "service_roles_tenant_id_tenants_id_fk", + "tableFrom": "service_roles", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "service_roles_id": { + "name": "service_roles_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "sessions": { + "name": "sessions", + "columns": { + "id": { + "name": "id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "idp_session_id": { + "name": "idp_session_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "amr": { + "name": "amr", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "acr": { + "name": "acr", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ip": { + "name": "ip", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "datetime(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP(3))" + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "datetime(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP(3))" + }, + "expires_at": { + "name": "expires_at", + "type": "datetime(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "datetime(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "sessions_idp_session_id_uidx": { + "name": "sessions_idp_session_id_uidx", + "columns": [ + "idp_session_id" + ], + "isUnique": true + }, + "sessions_user_idx": { + "name": "sessions_user_idx", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "sessions_expires_idx": { + "name": "sessions_expires_idx", + "columns": [ + "expires_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "sessions_tenant_id_tenants_id_fk": { + "name": "sessions_tenant_id_tenants_id_fk", + "tableFrom": "sessions", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "sessions_user_id_users_id_fk": { + "name": "sessions_user_id_users_id_fk", + "tableFrom": "sessions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "sessions_id": { + "name": "sessions_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "signing_keys": { + "name": "signing_keys", + "columns": { + "id": { + "name": "id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "kid": { + "name": "kid", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "use": { + "name": "use", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'sig'" + }, + "alg": { + "name": "alg", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "public_jwk": { + "name": "public_jwk", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "private_jwk_encrypted": { + "name": "private_jwk_encrypted", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "cert_pem": { + "name": "cert_pem", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "active": { + "name": "active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "datetime(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP(3))" + }, + "rotated_at": { + "name": "rotated_at", + "type": "datetime(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "not_after": { + "name": "not_after", + "type": "datetime(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "signing_keys_tenant_kid_uidx": { + "name": "signing_keys_tenant_kid_uidx", + "columns": [ + "tenant_id", + "kid" + ], + "isUnique": true + }, + "signing_keys_tenant_active_idx": { + "name": "signing_keys_tenant_active_idx", + "columns": [ + "tenant_id", + "active" + ], + "isUnique": false + } + }, + "foreignKeys": { + "signing_keys_tenant_id_tenants_id_fk": { + "name": "signing_keys_tenant_id_tenants_id_fk", + "tableFrom": "signing_keys", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "signing_keys_id": { + "name": "signing_keys_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "teams": { + "name": "teams", + "columns": { + "id": { + "name": "id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "department_id": { + "name": "department_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "code": { + "name": "code", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "leader_id": { + "name": "leader_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'active'" + }, + "created_at": { + "name": "created_at", + "type": "datetime(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP(3))" + }, + "updated_at": { + "name": "updated_at", + "type": "datetime(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP(3))" + } + }, + "indexes": { + "teams_tenant_idx": { + "name": "teams_tenant_idx", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "teams_department_idx": { + "name": "teams_department_idx", + "columns": [ + "department_id" + ], + "isUnique": false + }, + "teams_tenant_code_uidx": { + "name": "teams_tenant_code_uidx", + "columns": [ + "tenant_id", + "code" + ], + "isUnique": true + } + }, + "foreignKeys": { + "teams_tenant_id_tenants_id_fk": { + "name": "teams_tenant_id_tenants_id_fk", + "tableFrom": "teams", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "teams_department_id_departments_id_fk": { + "name": "teams_department_id_departments_id_fk", + "tableFrom": "teams", + "tableTo": "departments", + "columnsFrom": [ + "department_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "teams_leader_id_users_id_fk": { + "name": "teams_leader_id_users_id_fk", + "tableFrom": "teams", + "tableTo": "users", + "columnsFrom": [ + "leader_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "teams_id": { + "name": "teams_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "tenants": { + "name": "tenants", + "columns": { + "id": { + "name": "id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'active'" + }, + "created_at": { + "name": "created_at", + "type": "datetime(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP(3))" + }, + "updated_at": { + "name": "updated_at", + "type": "datetime(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP(3))" + } + }, + "indexes": { + "tenants_slug_uidx": { + "name": "tenants_slug_uidx", + "columns": [ + "slug" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "tenants_id": { + "name": "tenants_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "user_departments": { + "name": "user_departments", + "columns": { + "id": { + "name": "id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "department_id": { + "name": "department_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "position_id": { + "name": "position_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "job_title": { + "name": "job_title", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_primary": { + "name": "is_primary", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "started_at": { + "name": "started_at", + "type": "datetime(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP(3))" + }, + "ended_at": { + "name": "ended_at", + "type": "datetime(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "datetime(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP(3))" + } + }, + "indexes": { + "user_departments_user_idx": { + "name": "user_departments_user_idx", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "user_departments_dept_idx": { + "name": "user_departments_dept_idx", + "columns": [ + "department_id" + ], + "isUnique": false + }, + "user_departments_tenant_idx": { + "name": "user_departments_tenant_idx", + "columns": [ + "tenant_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "user_departments_tenant_id_tenants_id_fk": { + "name": "user_departments_tenant_id_tenants_id_fk", + "tableFrom": "user_departments", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_departments_user_id_users_id_fk": { + "name": "user_departments_user_id_users_id_fk", + "tableFrom": "user_departments", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_departments_department_id_departments_id_fk": { + "name": "user_departments_department_id_departments_id_fk", + "tableFrom": "user_departments", + "tableTo": "departments", + "columnsFrom": [ + "department_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_departments_position_id_positions_id_fk": { + "name": "user_departments_position_id_positions_id_fk", + "tableFrom": "user_departments", + "tableTo": "positions", + "columnsFrom": [ + "position_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_departments_id": { + "name": "user_departments_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "user_parts": { + "name": "user_parts", + "columns": { + "id": { + "name": "id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "part_id": { + "name": "part_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "job_title": { + "name": "job_title", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_primary": { + "name": "is_primary", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "started_at": { + "name": "started_at", + "type": "datetime(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP(3))" + }, + "ended_at": { + "name": "ended_at", + "type": "datetime(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "datetime(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP(3))" + } + }, + "indexes": { + "user_parts_user_idx": { + "name": "user_parts_user_idx", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "user_parts_part_idx": { + "name": "user_parts_part_idx", + "columns": [ + "part_id" + ], + "isUnique": false + }, + "user_parts_tenant_idx": { + "name": "user_parts_tenant_idx", + "columns": [ + "tenant_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "user_parts_tenant_id_tenants_id_fk": { + "name": "user_parts_tenant_id_tenants_id_fk", + "tableFrom": "user_parts", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_parts_user_id_users_id_fk": { + "name": "user_parts_user_id_users_id_fk", + "tableFrom": "user_parts", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_parts_part_id_parts_id_fk": { + "name": "user_parts_part_id_parts_id_fk", + "tableFrom": "user_parts", + "tableTo": "parts", + "columnsFrom": [ + "part_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_parts_id": { + "name": "user_parts_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "user_service_assignments": { + "name": "user_service_assignments", + "columns": { + "id": { + "name": "id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "service_type": { + "name": "service_type", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "service_ref_id": { + "name": "service_ref_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "service_role_id": { + "name": "service_role_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "attributes_json": { + "name": "attributes_json", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "granted_by": { + "name": "granted_by", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "granted_at": { + "name": "granted_at", + "type": "datetime(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP(3))" + }, + "expires_at": { + "name": "expires_at", + "type": "datetime(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "datetime(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "datetime(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP(3))" + } + }, + "indexes": { + "user_service_assignments_user_service_uidx": { + "name": "user_service_assignments_user_service_uidx", + "columns": [ + "tenant_id", + "user_id", + "service_type", + "service_ref_id" + ], + "isUnique": true + }, + "user_service_assignments_tenant_user_idx": { + "name": "user_service_assignments_tenant_user_idx", + "columns": [ + "tenant_id", + "user_id" + ], + "isUnique": false + }, + "user_service_assignments_tenant_service_idx": { + "name": "user_service_assignments_tenant_service_idx", + "columns": [ + "tenant_id", + "service_type", + "service_ref_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "user_service_assignments_tenant_id_tenants_id_fk": { + "name": "user_service_assignments_tenant_id_tenants_id_fk", + "tableFrom": "user_service_assignments", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_service_assignments_user_id_users_id_fk": { + "name": "user_service_assignments_user_id_users_id_fk", + "tableFrom": "user_service_assignments", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_service_assignments_service_role_id_service_roles_id_fk": { + "name": "user_service_assignments_service_role_id_service_roles_id_fk", + "tableFrom": "user_service_assignments", + "tableTo": "service_roles", + "columnsFrom": [ + "service_role_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_service_assignments_id": { + "name": "user_service_assignments_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "user_teams": { + "name": "user_teams", + "columns": { + "id": { + "name": "id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "team_id": { + "name": "team_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "job_title": { + "name": "job_title", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_primary": { + "name": "is_primary", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "started_at": { + "name": "started_at", + "type": "datetime(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP(3))" + }, + "ended_at": { + "name": "ended_at", + "type": "datetime(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "datetime(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP(3))" + } + }, + "indexes": { + "user_teams_user_idx": { + "name": "user_teams_user_idx", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "user_teams_team_idx": { + "name": "user_teams_team_idx", + "columns": [ + "team_id" + ], + "isUnique": false + }, + "user_teams_tenant_idx": { + "name": "user_teams_tenant_idx", + "columns": [ + "tenant_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "user_teams_tenant_id_tenants_id_fk": { + "name": "user_teams_tenant_id_tenants_id_fk", + "tableFrom": "user_teams", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_teams_user_id_users_id_fk": { + "name": "user_teams_user_id_users_id_fk", + "tableFrom": "user_teams", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_teams_team_id_teams_id_fk": { + "name": "user_teams_team_id_teams_id_fk", + "tableFrom": "user_teams", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_teams_id": { + "name": "user_teams_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "users": { + "name": "users", + "columns": { + "id": { + "name": "id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "varchar(320)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email_verified_at": { + "name": "email_verified_at", + "type": "datetime(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'user'" + }, + "status": { + "name": "status", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'active'" + }, + "given_name": { + "name": "given_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "family_name": { + "name": "family_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "phone_number": { + "name": "phone_number", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "phone_verified_at": { + "name": "phone_verified_at", + "type": "datetime(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "avatar_url": { + "name": "avatar_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "locale": { + "name": "locale", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "('ko-KR')" + }, + "zoneinfo": { + "name": "zoneinfo", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "('Asia/Seoul')" + }, + "bio": { + "name": "bio", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "birthdate": { + "name": "birthdate", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "address_street": { + "name": "address_street", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "address_locality": { + "name": "address_locality", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "address_region": { + "name": "address_region", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "address_postal_code": { + "name": "address_postal_code", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "address_country": { + "name": "address_country", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "datetime(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP(3))" + }, + "updated_at": { + "name": "updated_at", + "type": "datetime(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP(3))" + } + }, + "indexes": { + "users_tenant_email_uidx": { + "name": "users_tenant_email_uidx", + "columns": [ + "tenant_id", + "email" + ], + "isUnique": true + }, + "users_tenant_username_uidx": { + "name": "users_tenant_username_uidx", + "columns": [ + "tenant_id", + "username" + ], + "isUnique": true + }, + "users_tenant_idx": { + "name": "users_tenant_idx", + "columns": [ + "tenant_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "users_tenant_id_tenants_id_fk": { + "name": "users_tenant_id_tenants_id_fk", + "tableFrom": "users", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "users_id": { + "name": "users_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "webauthn_challenges": { + "name": "webauthn_challenges", + "columns": { + "id": { + "name": "id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "challenge": { + "name": "challenge", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "datetime(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "used_at": { + "name": "used_at", + "type": "datetime(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "webauthn_challenges_tenant_challenge_uidx": { + "name": "webauthn_challenges_tenant_challenge_uidx", + "columns": [ + "tenant_id", + "challenge" + ], + "isUnique": true + }, + "webauthn_challenges_tenant_expires_idx": { + "name": "webauthn_challenges_tenant_expires_idx", + "columns": [ + "tenant_id", + "expires_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "webauthn_challenges_tenant_id_tenants_id_fk": { + "name": "webauthn_challenges_tenant_id_tenants_id_fk", + "tableFrom": "webauthn_challenges", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "webauthn_challenges_user_id_users_id_fk": { + "name": "webauthn_challenges_user_id_users_id_fk", + "tableFrom": "webauthn_challenges", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "webauthn_challenges_id": { + "name": "webauthn_challenges_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + } + }, + "views": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "tables": {}, + "indexes": {} + } +} \ No newline at end of file diff --git a/drizzle/mysql/meta/_journal.json b/drizzle/mysql/meta/_journal.json index b03e2b5..2d05c0e 100644 --- a/drizzle/mysql/meta/_journal.json +++ b/drizzle/mysql/meta/_journal.json @@ -22,6 +22,13 @@ "when": 1783222882349, "tag": "0002_mysterious_gravity", "breakpoints": true + }, + { + "idx": 3, + "version": "5", + "when": 1783246072878, + "tag": "0003_shiny_abomination", + "breakpoints": true } ] } \ No newline at end of file diff --git a/drizzle/pg/0003_charming_the_fury.sql b/drizzle/pg/0003_charming_the_fury.sql new file mode 100644 index 0000000..653d512 --- /dev/null +++ b/drizzle/pg/0003_charming_the_fury.sql @@ -0,0 +1,5 @@ +ALTER TABLE "users" ADD COLUMN "address_street" text;--> statement-breakpoint +ALTER TABLE "users" ADD COLUMN "address_locality" text;--> statement-breakpoint +ALTER TABLE "users" ADD COLUMN "address_region" text;--> statement-breakpoint +ALTER TABLE "users" ADD COLUMN "address_postal_code" text;--> statement-breakpoint +ALTER TABLE "users" ADD COLUMN "address_country" text; \ No newline at end of file diff --git a/drizzle/pg/meta/0003_snapshot.json b/drizzle/pg/meta/0003_snapshot.json new file mode 100644 index 0000000..a4b77cc --- /dev/null +++ b/drizzle/pg/meta/0003_snapshot.json @@ -0,0 +1,4256 @@ +{ + "id": "986db2b0-8890-4c93-ac48-2d7659dda362", + "prevId": "4c043de9-2f2b-4d13-8658-6490708f9008", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.audit_events": { + "name": "audit_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sp_or_client_id": { + "name": "sp_or_client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "ip": { + "name": "ip", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "detail_json": { + "name": "detail_json", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "hash": { + "name": "hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "audit_events_tenant_kind_idx": { + "name": "audit_events_tenant_kind_idx", + "columns": [ + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "audit_events_tenant_created_idx": { + "name": "audit_events_tenant_created_idx", + "columns": [ + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "audit_events_user_idx": { + "name": "audit_events_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "audit_events_tenant_id_tenants_id_fk": { + "name": "audit_events_tenant_id_tenants_id_fk", + "tableFrom": "audit_events", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "audit_events_user_id_users_id_fk": { + "name": "audit_events_user_id_users_id_fk", + "tableFrom": "audit_events", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.client_skins": { + "name": "client_skins", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_type": { + "name": "client_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_ref_id": { + "name": "client_ref_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "skin_type": { + "name": "skin_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'login'" + }, + "fetch_url": { + "name": "fetch_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "fetch_secret": { + "name": "fetch_secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cache_ttl_seconds": { + "name": "cache_ttl_seconds", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 3600 + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "client_skins_unique": { + "name": "client_skins_unique", + "columns": [ + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "client_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "client_ref_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "skin_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "client_skins_tenant_id_tenants_id_fk": { + "name": "client_skins_tenant_id_tenants_id_fk", + "tableFrom": "client_skins", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.credentials": { + "name": "credentials", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "secret": { + "name": "secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "credential_id": { + "name": "credential_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "counter": { + "name": "counter", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "transports": { + "name": "transports", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "totp_owner_id": { + "name": "totp_owner_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": false + }, + "used_at": { + "name": "used_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "credentials_user_idx": { + "name": "credentials_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credentials_user_type_idx": { + "name": "credentials_user_type_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credentials_webauthn_credential_id_uidx": { + "name": "credentials_webauthn_credential_id_uidx", + "columns": [ + { + "expression": "credential_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credentials_totp_owner_uidx": { + "name": "credentials_totp_owner_uidx", + "columns": [ + { + "expression": "totp_owner_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "credentials_user_id_users_id_fk": { + "name": "credentials_user_id_users_id_fk", + "tableFrom": "credentials", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.departments": { + "name": "departments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_id": { + "name": "parent_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "manager_id": { + "name": "manager_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "display_order": { + "name": "display_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "departments_tenant_idx": { + "name": "departments_tenant_idx", + "columns": [ + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "departments_parent_idx": { + "name": "departments_parent_idx", + "columns": [ + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "departments_tenant_code_uidx": { + "name": "departments_tenant_code_uidx", + "columns": [ + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "departments_tenant_id_tenants_id_fk": { + "name": "departments_tenant_id_tenants_id_fk", + "tableFrom": "departments", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "departments_parent_id_departments_id_fk": { + "name": "departments_parent_id_departments_id_fk", + "tableFrom": "departments", + "tableTo": "departments", + "columnsFrom": [ + "parent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "departments_manager_id_users_id_fk": { + "name": "departments_manager_id_users_id_fk", + "tableFrom": "departments", + "tableTo": "users", + "columnsFrom": [ + "manager_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.identities": { + "name": "identities", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "raw_profile_json": { + "name": "raw_profile_json", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "linked_at": { + "name": "linked_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_login_at": { + "name": "last_login_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "identities_tenant_provider_subject_uidx": { + "name": "identities_tenant_provider_subject_uidx", + "columns": [ + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "identities_user_idx": { + "name": "identities_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "identities_tenant_id_tenants_id_fk": { + "name": "identities_tenant_id_tenants_id_fk", + "tableFrom": "identities", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "identities_user_id_users_id_fk": { + "name": "identities_user_id_users_id_fk", + "tableFrom": "identities", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.identity_providers": { + "name": "identity_providers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_secret_enc": { + "name": "client_secret_enc", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "discovery_url": { + "name": "discovery_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata_xml": { + "name": "metadata_xml", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "config_json": { + "name": "config_json", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idp_tenant_idx": { + "name": "idp_tenant_idx", + "columns": [ + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idp_tenant_name_uidx": { + "name": "idp_tenant_name_uidx", + "columns": [ + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "identity_providers_tenant_id_tenants_id_fk": { + "name": "identity_providers_tenant_id_tenants_id_fk", + "tableFrom": "identity_providers", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oidc_clients": { + "name": "oidc_clients", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_secret_hash": { + "name": "client_secret_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "redirect_uris": { + "name": "redirect_uris", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "post_logout_redirect_uris": { + "name": "post_logout_redirect_uris", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "frontchannel_logout_uri": { + "name": "frontchannel_logout_uri", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "frontchannel_logout_session_required": { + "name": "frontchannel_logout_session_required", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "backchannel_logout_uri": { + "name": "backchannel_logout_uri", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "backchannel_logout_session_required": { + "name": "backchannel_logout_session_required", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "scopes": { + "name": "scopes", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'openid'" + }, + "grant_types": { + "name": "grant_types", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'authorization_code,refresh_token'" + }, + "response_types": { + "name": "response_types", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'code'" + }, + "token_endpoint_auth_method": { + "name": "token_endpoint_auth_method", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'client_secret_basic'" + }, + "require_pkce": { + "name": "require_pkce", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "allow_wildcard_redirect_uri": { + "name": "allow_wildcard_redirect_uri", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "id_token_signed_response_alg": { + "name": "id_token_signed_response_alg", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'RS256'" + }, + "jwks_uri": { + "name": "jwks_uri", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "jwks": { + "name": "jwks", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "oidc_clients_tenant_client_id_uidx": { + "name": "oidc_clients_tenant_client_id_uidx", + "columns": [ + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oidc_clients_tenant_idx": { + "name": "oidc_clients_tenant_idx", + "columns": [ + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "oidc_clients_tenant_id_tenants_id_fk": { + "name": "oidc_clients_tenant_id_tenants_id_fk", + "tableFrom": "oidc_clients", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oidc_grants": { + "name": "oidc_grants", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "code_hash": { + "name": "code_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "code_challenge": { + "name": "code_challenge", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "code_challenge_method": { + "name": "code_challenge_method", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "redirect_uri": { + "name": "redirect_uri", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "nonce": { + "name": "nonce", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "acr": { + "name": "acr", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true + }, + "used_at": { + "name": "used_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "oidc_grants_code_uidx": { + "name": "oidc_grants_code_uidx", + "columns": [ + { + "expression": "code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oidc_grants_code_hash_uidx": { + "name": "oidc_grants_code_hash_uidx", + "columns": [ + { + "expression": "code_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oidc_grants_tenant_client_idx": { + "name": "oidc_grants_tenant_client_idx", + "columns": [ + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oidc_grants_expires_idx": { + "name": "oidc_grants_expires_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "oidc_grants_tenant_id_tenants_id_fk": { + "name": "oidc_grants_tenant_id_tenants_id_fk", + "tableFrom": "oidc_grants", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oidc_grants_user_id_users_id_fk": { + "name": "oidc_grants_user_id_users_id_fk", + "tableFrom": "oidc_grants", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oidc_grants_session_id_sessions_id_fk": { + "name": "oidc_grants_session_id_sessions_id_fk", + "tableFrom": "oidc_grants", + "tableTo": "sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oidc_refresh_tokens": { + "name": "oidc_refresh_tokens", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": false + }, + "replaced_by_id": { + "name": "replaced_by_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "oidc_refresh_tokens_hash_uidx": { + "name": "oidc_refresh_tokens_hash_uidx", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oidc_refresh_tokens_user_idx": { + "name": "oidc_refresh_tokens_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "oidc_refresh_tokens_tenant_id_tenants_id_fk": { + "name": "oidc_refresh_tokens_tenant_id_tenants_id_fk", + "tableFrom": "oidc_refresh_tokens", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oidc_refresh_tokens_user_id_users_id_fk": { + "name": "oidc_refresh_tokens_user_id_users_id_fk", + "tableFrom": "oidc_refresh_tokens", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oidc_refresh_tokens_session_id_sessions_id_fk": { + "name": "oidc_refresh_tokens_session_id_sessions_id_fk", + "tableFrom": "oidc_refresh_tokens", + "tableTo": "sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.parts": { + "name": "parts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "leader_id": { + "name": "leader_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "parts_tenant_idx": { + "name": "parts_tenant_idx", + "columns": [ + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "parts_team_idx": { + "name": "parts_team_idx", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "parts_tenant_code_uidx": { + "name": "parts_tenant_code_uidx", + "columns": [ + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "parts_tenant_id_tenants_id_fk": { + "name": "parts_tenant_id_tenants_id_fk", + "tableFrom": "parts", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "parts_team_id_teams_id_fk": { + "name": "parts_team_id_teams_id_fk", + "tableFrom": "parts", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "parts_leader_id_users_id_fk": { + "name": "parts_leader_id_users_id_fk", + "tableFrom": "parts", + "tableTo": "users", + "columnsFrom": [ + "leader_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.password_reset_tokens": { + "name": "password_reset_tokens", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true + }, + "used_at": { + "name": "used_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "password_reset_tokens_user_idx": { + "name": "password_reset_tokens_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "password_reset_tokens_hash_uidx": { + "name": "password_reset_tokens_hash_uidx", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "password_reset_tokens_user_id_users_id_fk": { + "name": "password_reset_tokens_user_id_users_id_fk", + "tableFrom": "password_reset_tokens", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.positions": { + "name": "positions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "level": { + "name": "level", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "positions_tenant_idx": { + "name": "positions_tenant_idx", + "columns": [ + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "positions_tenant_code_uidx": { + "name": "positions_tenant_code_uidx", + "columns": [ + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "positions_tenant_id_tenants_id_fk": { + "name": "positions_tenant_id_tenants_id_fk", + "tableFrom": "positions", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.rate_limits": { + "name": "rate_limits", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "count": { + "name": "count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.saml_authn_request_ids": { + "name": "saml_authn_request_ids", + "schema": "", + "columns": { + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "request_id": { + "name": "request_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sp_entity_id": { + "name": "sp_entity_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "seen_at": { + "name": "seen_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "saml_authn_request_ids_tenant_req_uidx": { + "name": "saml_authn_request_ids_tenant_req_uidx", + "columns": [ + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "request_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "saml_authn_request_ids_expires_idx": { + "name": "saml_authn_request_ids_expires_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "saml_authn_request_ids_tenant_id_tenants_id_fk": { + "name": "saml_authn_request_ids_tenant_id_tenants_id_fk", + "tableFrom": "saml_authn_request_ids", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.saml_sessions": { + "name": "saml_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sp_id": { + "name": "sp_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "session_index": { + "name": "session_index", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name_id": { + "name": "name_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name_id_format": { + "name": "name_id_format", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "not_on_or_after": { + "name": "not_on_or_after", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "ended_at": { + "name": "ended_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "saml_sessions_session_index_uidx": { + "name": "saml_sessions_session_index_uidx", + "columns": [ + { + "expression": "session_index", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "saml_sessions_tenant_sp_idx": { + "name": "saml_sessions_tenant_sp_idx", + "columns": [ + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sp_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "saml_sessions_tenant_id_tenants_id_fk": { + "name": "saml_sessions_tenant_id_tenants_id_fk", + "tableFrom": "saml_sessions", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "saml_sessions_sp_id_saml_sps_id_fk": { + "name": "saml_sessions_sp_id_saml_sps_id_fk", + "tableFrom": "saml_sessions", + "tableTo": "saml_sps", + "columnsFrom": [ + "sp_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "saml_sessions_user_id_users_id_fk": { + "name": "saml_sessions_user_id_users_id_fk", + "tableFrom": "saml_sessions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "saml_sessions_session_id_sessions_id_fk": { + "name": "saml_sessions_session_id_sessions_id_fk", + "tableFrom": "saml_sessions", + "tableTo": "sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.saml_slo_states": { + "name": "saml_slo_states", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "idp_session_record_id": { + "name": "idp_session_record_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "initiating_sp_entity_id": { + "name": "initiating_sp_entity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "in_response_to": { + "name": "in_response_to", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "initiator_slo_url": { + "name": "initiator_slo_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "completion_uri": { + "name": "completion_uri", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pending_sp_data_json": { + "name": "pending_sp_data_json", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "saml_slo_states_tenant_id_tenants_id_fk": { + "name": "saml_slo_states_tenant_id_tenants_id_fk", + "tableFrom": "saml_slo_states", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "saml_slo_states_user_id_users_id_fk": { + "name": "saml_slo_states_user_id_users_id_fk", + "tableFrom": "saml_slo_states", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.saml_sps": { + "name": "saml_sps", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entity_id": { + "name": "entity_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "acs_url": { + "name": "acs_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "acs_binding": { + "name": "acs_binding", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST'" + }, + "slo_url": { + "name": "slo_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "slo_binding": { + "name": "slo_binding", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cert": { + "name": "cert", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name_id_format": { + "name": "name_id_format", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress'" + }, + "sign_assertion": { + "name": "sign_assertion", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "sign_response": { + "name": "sign_response", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "encrypt_assertion": { + "name": "encrypt_assertion", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "want_authn_requests_signed": { + "name": "want_authn_requests_signed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "attribute_mapping_json": { + "name": "attribute_mapping_json", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "allowed_attributes": { + "name": "allowed_attributes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "saml_sps_tenant_entity_id_uidx": { + "name": "saml_sps_tenant_entity_id_uidx", + "columns": [ + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "saml_sps_tenant_idx": { + "name": "saml_sps_tenant_idx", + "columns": [ + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "saml_sps_tenant_id_tenants_id_fk": { + "name": "saml_sps_tenant_id_tenants_id_fk", + "tableFrom": "saml_sps", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.service_roles": { + "name": "service_roles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "service_type": { + "name": "service_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "service_ref_id": { + "name": "service_ref_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "display_order": { + "name": "display_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "service_roles_service_key_uidx": { + "name": "service_roles_service_key_uidx", + "columns": [ + { + "expression": "service_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "service_ref_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "service_roles_tenant_service_idx": { + "name": "service_roles_tenant_service_idx", + "columns": [ + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "service_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "service_ref_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "service_roles_tenant_id_tenants_id_fk": { + "name": "service_roles_tenant_id_tenants_id_fk", + "tableFrom": "service_roles", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sessions": { + "name": "sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "idp_session_id": { + "name": "idp_session_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amr": { + "name": "amr", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "acr": { + "name": "acr", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ip": { + "name": "ip", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "sessions_idp_session_id_uidx": { + "name": "sessions_idp_session_id_uidx", + "columns": [ + { + "expression": "idp_session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sessions_user_idx": { + "name": "sessions_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sessions_expires_idx": { + "name": "sessions_expires_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "sessions_tenant_id_tenants_id_fk": { + "name": "sessions_tenant_id_tenants_id_fk", + "tableFrom": "sessions", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "sessions_user_id_users_id_fk": { + "name": "sessions_user_id_users_id_fk", + "tableFrom": "sessions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.signing_keys": { + "name": "signing_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kid": { + "name": "kid", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "use": { + "name": "use", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'sig'" + }, + "alg": { + "name": "alg", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "public_jwk": { + "name": "public_jwk", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "private_jwk_encrypted": { + "name": "private_jwk_encrypted", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cert_pem": { + "name": "cert_pem", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "active": { + "name": "active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "rotated_at": { + "name": "rotated_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": false + }, + "not_after": { + "name": "not_after", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "signing_keys_tenant_kid_uidx": { + "name": "signing_keys_tenant_kid_uidx", + "columns": [ + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kid", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "signing_keys_tenant_active_idx": { + "name": "signing_keys_tenant_active_idx", + "columns": [ + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "active", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "signing_keys_tenant_one_active_uidx": { + "name": "signing_keys_tenant_one_active_uidx", + "columns": [ + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"signing_keys\".\"active\"", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "signing_keys_tenant_id_tenants_id_fk": { + "name": "signing_keys_tenant_id_tenants_id_fk", + "tableFrom": "signing_keys", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.teams": { + "name": "teams", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "department_id": { + "name": "department_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "leader_id": { + "name": "leader_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "teams_tenant_idx": { + "name": "teams_tenant_idx", + "columns": [ + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "teams_department_idx": { + "name": "teams_department_idx", + "columns": [ + { + "expression": "department_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "teams_tenant_code_uidx": { + "name": "teams_tenant_code_uidx", + "columns": [ + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "teams_tenant_id_tenants_id_fk": { + "name": "teams_tenant_id_tenants_id_fk", + "tableFrom": "teams", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "teams_department_id_departments_id_fk": { + "name": "teams_department_id_departments_id_fk", + "tableFrom": "teams", + "tableTo": "departments", + "columnsFrom": [ + "department_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "teams_leader_id_users_id_fk": { + "name": "teams_leader_id_users_id_fk", + "tableFrom": "teams", + "tableTo": "users", + "columnsFrom": [ + "leader_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tenants": { + "name": "tenants", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tenants_slug_uidx": { + "name": "tenants_slug_uidx", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_departments": { + "name": "user_departments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "department_id": { + "name": "department_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "position_id": { + "name": "position_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "job_title": { + "name": "job_title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_primary": { + "name": "is_primary", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "ended_at": { + "name": "ended_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "user_departments_user_idx": { + "name": "user_departments_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_departments_dept_idx": { + "name": "user_departments_dept_idx", + "columns": [ + { + "expression": "department_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_departments_tenant_idx": { + "name": "user_departments_tenant_idx", + "columns": [ + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_departments_tenant_id_tenants_id_fk": { + "name": "user_departments_tenant_id_tenants_id_fk", + "tableFrom": "user_departments", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_departments_user_id_users_id_fk": { + "name": "user_departments_user_id_users_id_fk", + "tableFrom": "user_departments", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_departments_department_id_departments_id_fk": { + "name": "user_departments_department_id_departments_id_fk", + "tableFrom": "user_departments", + "tableTo": "departments", + "columnsFrom": [ + "department_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_departments_position_id_positions_id_fk": { + "name": "user_departments_position_id_positions_id_fk", + "tableFrom": "user_departments", + "tableTo": "positions", + "columnsFrom": [ + "position_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_parts": { + "name": "user_parts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "part_id": { + "name": "part_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "job_title": { + "name": "job_title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_primary": { + "name": "is_primary", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "ended_at": { + "name": "ended_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "user_parts_user_idx": { + "name": "user_parts_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_parts_part_idx": { + "name": "user_parts_part_idx", + "columns": [ + { + "expression": "part_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_parts_tenant_idx": { + "name": "user_parts_tenant_idx", + "columns": [ + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_parts_tenant_id_tenants_id_fk": { + "name": "user_parts_tenant_id_tenants_id_fk", + "tableFrom": "user_parts", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_parts_user_id_users_id_fk": { + "name": "user_parts_user_id_users_id_fk", + "tableFrom": "user_parts", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_parts_part_id_parts_id_fk": { + "name": "user_parts_part_id_parts_id_fk", + "tableFrom": "user_parts", + "tableTo": "parts", + "columnsFrom": [ + "part_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_service_assignments": { + "name": "user_service_assignments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "service_type": { + "name": "service_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "service_ref_id": { + "name": "service_ref_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "service_role_id": { + "name": "service_role_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "attributes_json": { + "name": "attributes_json", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "granted_by": { + "name": "granted_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "granted_at": { + "name": "granted_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "user_service_assignments_user_service_uidx": { + "name": "user_service_assignments_user_service_uidx", + "columns": [ + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "service_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "service_ref_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_service_assignments_tenant_user_idx": { + "name": "user_service_assignments_tenant_user_idx", + "columns": [ + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_service_assignments_tenant_service_idx": { + "name": "user_service_assignments_tenant_service_idx", + "columns": [ + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "service_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "service_ref_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_service_assignments_tenant_id_tenants_id_fk": { + "name": "user_service_assignments_tenant_id_tenants_id_fk", + "tableFrom": "user_service_assignments", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_service_assignments_user_id_users_id_fk": { + "name": "user_service_assignments_user_id_users_id_fk", + "tableFrom": "user_service_assignments", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_service_assignments_service_role_id_service_roles_id_fk": { + "name": "user_service_assignments_service_role_id_service_roles_id_fk", + "tableFrom": "user_service_assignments", + "tableTo": "service_roles", + "columnsFrom": [ + "service_role_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_teams": { + "name": "user_teams", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "job_title": { + "name": "job_title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_primary": { + "name": "is_primary", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "ended_at": { + "name": "ended_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "user_teams_user_idx": { + "name": "user_teams_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_teams_team_idx": { + "name": "user_teams_team_idx", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_teams_tenant_idx": { + "name": "user_teams_tenant_idx", + "columns": [ + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_teams_tenant_id_tenants_id_fk": { + "name": "user_teams_tenant_id_tenants_id_fk", + "tableFrom": "user_teams", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_teams_user_id_users_id_fk": { + "name": "user_teams_user_id_users_id_fk", + "tableFrom": "user_teams", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_teams_team_id_teams_id_fk": { + "name": "user_teams_team_id_teams_id_fk", + "tableFrom": "user_teams", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified_at": { + "name": "email_verified_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": false + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'user'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "given_name": { + "name": "given_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "family_name": { + "name": "family_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "phone_number": { + "name": "phone_number", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "phone_verified_at": { + "name": "phone_verified_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": false + }, + "avatar_url": { + "name": "avatar_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "locale": { + "name": "locale", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'ko-KR'" + }, + "zoneinfo": { + "name": "zoneinfo", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'Asia/Seoul'" + }, + "bio": { + "name": "bio", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "birthdate": { + "name": "birthdate", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "address_street": { + "name": "address_street", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "address_locality": { + "name": "address_locality", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "address_region": { + "name": "address_region", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "address_postal_code": { + "name": "address_postal_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "address_country": { + "name": "address_country", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "users_tenant_email_uidx": { + "name": "users_tenant_email_uidx", + "columns": [ + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "users_tenant_username_uidx": { + "name": "users_tenant_username_uidx", + "columns": [ + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "username", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "users_tenant_idx": { + "name": "users_tenant_idx", + "columns": [ + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "users_tenant_id_tenants_id_fk": { + "name": "users_tenant_id_tenants_id_fk", + "tableFrom": "users", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.webauthn_challenges": { + "name": "webauthn_challenges", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "challenge": { + "name": "challenge", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true + }, + "used_at": { + "name": "used_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "webauthn_challenges_tenant_challenge_uidx": { + "name": "webauthn_challenges_tenant_challenge_uidx", + "columns": [ + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "challenge", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "webauthn_challenges_tenant_expires_idx": { + "name": "webauthn_challenges_tenant_expires_idx", + "columns": [ + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "webauthn_challenges_tenant_id_tenants_id_fk": { + "name": "webauthn_challenges_tenant_id_tenants_id_fk", + "tableFrom": "webauthn_challenges", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "webauthn_challenges_user_id_users_id_fk": { + "name": "webauthn_challenges_user_id_users_id_fk", + "tableFrom": "webauthn_challenges", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/pg/meta/_journal.json b/drizzle/pg/meta/_journal.json index f13cc44..43e54d7 100644 --- a/drizzle/pg/meta/_journal.json +++ b/drizzle/pg/meta/_journal.json @@ -22,6 +22,13 @@ "when": 1783222882043, "tag": "0002_damp_network", "breakpoints": true + }, + { + "idx": 3, + "version": "7", + "when": 1783246072531, + "tag": "0003_charming_the_fury", + "breakpoints": true } ] } \ No newline at end of file diff --git a/drizzle/sqlite/0003_white_speed_demon.sql b/drizzle/sqlite/0003_white_speed_demon.sql new file mode 100644 index 0000000..d76083f --- /dev/null +++ b/drizzle/sqlite/0003_white_speed_demon.sql @@ -0,0 +1,5 @@ +ALTER TABLE `users` ADD `address_street` text;--> statement-breakpoint +ALTER TABLE `users` ADD `address_locality` text;--> statement-breakpoint +ALTER TABLE `users` ADD `address_region` text;--> statement-breakpoint +ALTER TABLE `users` ADD `address_postal_code` text;--> statement-breakpoint +ALTER TABLE `users` ADD `address_country` text; \ No newline at end of file diff --git a/drizzle/sqlite/meta/0003_snapshot.json b/drizzle/sqlite/meta/0003_snapshot.json new file mode 100644 index 0000000..513e322 --- /dev/null +++ b/drizzle/sqlite/meta/0003_snapshot.json @@ -0,0 +1,3793 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "fac94d5e-7925-4f87-a008-a847aab196d2", + "prevId": "dc1feb1c-0132-4963-8027-a1e17ffd2484", + "tables": { + "audit_events": { + "name": "audit_events", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sp_or_client_id": { + "name": "sp_or_client_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ip": { + "name": "ip", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "detail_json": { + "name": "detail_json", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "hash": { + "name": "hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + } + }, + "indexes": { + "audit_events_tenant_kind_idx": { + "name": "audit_events_tenant_kind_idx", + "columns": [ + "tenant_id", + "kind" + ], + "isUnique": false + }, + "audit_events_tenant_created_idx": { + "name": "audit_events_tenant_created_idx", + "columns": [ + "tenant_id", + "created_at" + ], + "isUnique": false + }, + "audit_events_user_idx": { + "name": "audit_events_user_idx", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "audit_events_tenant_id_tenants_id_fk": { + "name": "audit_events_tenant_id_tenants_id_fk", + "tableFrom": "audit_events", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "audit_events_user_id_users_id_fk": { + "name": "audit_events_user_id_users_id_fk", + "tableFrom": "audit_events", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "client_skins": { + "name": "client_skins", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_type": { + "name": "client_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_ref_id": { + "name": "client_ref_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "skin_type": { + "name": "skin_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'login'" + }, + "fetch_url": { + "name": "fetch_url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "fetch_secret": { + "name": "fetch_secret", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cache_ttl_seconds": { + "name": "cache_ttl_seconds", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 3600 + }, + "enabled": { + "name": "enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "client_skins_unique": { + "name": "client_skins_unique", + "columns": [ + "tenant_id", + "client_type", + "client_ref_id", + "skin_type" + ], + "isUnique": true + } + }, + "foreignKeys": { + "client_skins_tenant_id_tenants_id_fk": { + "name": "client_skins_tenant_id_tenants_id_fk", + "tableFrom": "client_skins", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "credentials": { + "name": "credentials", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "secret": { + "name": "secret", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "credential_id": { + "name": "credential_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "counter": { + "name": "counter", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "transports": { + "name": "transports", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "totp_owner_id": { + "name": "totp_owner_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "used_at": { + "name": "used_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + } + }, + "indexes": { + "credentials_user_idx": { + "name": "credentials_user_idx", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "credentials_user_type_idx": { + "name": "credentials_user_type_idx", + "columns": [ + "user_id", + "type" + ], + "isUnique": false + }, + "credentials_webauthn_credential_id_uidx": { + "name": "credentials_webauthn_credential_id_uidx", + "columns": [ + "credential_id" + ], + "isUnique": true + }, + "credentials_totp_owner_uidx": { + "name": "credentials_totp_owner_uidx", + "columns": [ + "totp_owner_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "credentials_user_id_users_id_fk": { + "name": "credentials_user_id_users_id_fk", + "tableFrom": "credentials", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "departments": { + "name": "departments", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "parent_id": { + "name": "parent_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "manager_id": { + "name": "manager_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "display_order": { + "name": "display_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'active'" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + } + }, + "indexes": { + "departments_tenant_idx": { + "name": "departments_tenant_idx", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "departments_parent_idx": { + "name": "departments_parent_idx", + "columns": [ + "parent_id" + ], + "isUnique": false + }, + "departments_tenant_code_uidx": { + "name": "departments_tenant_code_uidx", + "columns": [ + "tenant_id", + "code" + ], + "isUnique": true + } + }, + "foreignKeys": { + "departments_tenant_id_tenants_id_fk": { + "name": "departments_tenant_id_tenants_id_fk", + "tableFrom": "departments", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "departments_parent_id_departments_id_fk": { + "name": "departments_parent_id_departments_id_fk", + "tableFrom": "departments", + "tableTo": "departments", + "columnsFrom": [ + "parent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "departments_manager_id_users_id_fk": { + "name": "departments_manager_id_users_id_fk", + "tableFrom": "departments", + "tableTo": "users", + "columnsFrom": [ + "manager_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "identities": { + "name": "identities", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "raw_profile_json": { + "name": "raw_profile_json", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "linked_at": { + "name": "linked_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + }, + "last_login_at": { + "name": "last_login_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "identities_tenant_provider_subject_uidx": { + "name": "identities_tenant_provider_subject_uidx", + "columns": [ + "tenant_id", + "provider", + "subject" + ], + "isUnique": true + }, + "identities_user_idx": { + "name": "identities_user_idx", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "identities_tenant_id_tenants_id_fk": { + "name": "identities_tenant_id_tenants_id_fk", + "tableFrom": "identities", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "identities_user_id_users_id_fk": { + "name": "identities_user_id_users_id_fk", + "tableFrom": "identities", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "identity_providers": { + "name": "identity_providers", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "client_secret_enc": { + "name": "client_secret_enc", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "discovery_url": { + "name": "discovery_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "metadata_xml": { + "name": "metadata_xml", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scopes": { + "name": "scopes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "config_json": { + "name": "config_json", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enabled": { + "name": "enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + } + }, + "indexes": { + "idp_tenant_idx": { + "name": "idp_tenant_idx", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "idp_tenant_name_uidx": { + "name": "idp_tenant_name_uidx", + "columns": [ + "tenant_id", + "name" + ], + "isUnique": true + } + }, + "foreignKeys": { + "identity_providers_tenant_id_tenants_id_fk": { + "name": "identity_providers_tenant_id_tenants_id_fk", + "tableFrom": "identity_providers", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "oidc_clients": { + "name": "oidc_clients", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_secret_hash": { + "name": "client_secret_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "redirect_uris": { + "name": "redirect_uris", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "post_logout_redirect_uris": { + "name": "post_logout_redirect_uris", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "frontchannel_logout_uri": { + "name": "frontchannel_logout_uri", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "frontchannel_logout_session_required": { + "name": "frontchannel_logout_session_required", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "backchannel_logout_uri": { + "name": "backchannel_logout_uri", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "backchannel_logout_session_required": { + "name": "backchannel_logout_session_required", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "scopes": { + "name": "scopes", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'openid'" + }, + "grant_types": { + "name": "grant_types", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'authorization_code,refresh_token'" + }, + "response_types": { + "name": "response_types", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'code'" + }, + "token_endpoint_auth_method": { + "name": "token_endpoint_auth_method", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'client_secret_basic'" + }, + "require_pkce": { + "name": "require_pkce", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "allow_wildcard_redirect_uri": { + "name": "allow_wildcard_redirect_uri", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "id_token_signed_response_alg": { + "name": "id_token_signed_response_alg", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'RS256'" + }, + "jwks_uri": { + "name": "jwks_uri", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "jwks": { + "name": "jwks", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enabled": { + "name": "enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + } + }, + "indexes": { + "oidc_clients_tenant_client_id_uidx": { + "name": "oidc_clients_tenant_client_id_uidx", + "columns": [ + "tenant_id", + "client_id" + ], + "isUnique": true + }, + "oidc_clients_tenant_idx": { + "name": "oidc_clients_tenant_idx", + "columns": [ + "tenant_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "oidc_clients_tenant_id_tenants_id_fk": { + "name": "oidc_clients_tenant_id_tenants_id_fk", + "tableFrom": "oidc_clients", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "oidc_grants": { + "name": "oidc_grants", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "code_hash": { + "name": "code_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "code_challenge": { + "name": "code_challenge", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "code_challenge_method": { + "name": "code_challenge_method", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "redirect_uri": { + "name": "redirect_uri", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "nonce": { + "name": "nonce", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "acr": { + "name": "acr", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "used_at": { + "name": "used_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + } + }, + "indexes": { + "oidc_grants_code_uidx": { + "name": "oidc_grants_code_uidx", + "columns": [ + "code" + ], + "isUnique": true + }, + "oidc_grants_code_hash_uidx": { + "name": "oidc_grants_code_hash_uidx", + "columns": [ + "code_hash" + ], + "isUnique": true + }, + "oidc_grants_tenant_client_idx": { + "name": "oidc_grants_tenant_client_idx", + "columns": [ + "tenant_id", + "client_id" + ], + "isUnique": false + }, + "oidc_grants_expires_idx": { + "name": "oidc_grants_expires_idx", + "columns": [ + "expires_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "oidc_grants_tenant_id_tenants_id_fk": { + "name": "oidc_grants_tenant_id_tenants_id_fk", + "tableFrom": "oidc_grants", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oidc_grants_user_id_users_id_fk": { + "name": "oidc_grants_user_id_users_id_fk", + "tableFrom": "oidc_grants", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oidc_grants_session_id_sessions_id_fk": { + "name": "oidc_grants_session_id_sessions_id_fk", + "tableFrom": "oidc_grants", + "tableTo": "sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "oidc_refresh_tokens": { + "name": "oidc_refresh_tokens", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "replaced_by_id": { + "name": "replaced_by_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + } + }, + "indexes": { + "oidc_refresh_tokens_hash_uidx": { + "name": "oidc_refresh_tokens_hash_uidx", + "columns": [ + "token_hash" + ], + "isUnique": true + }, + "oidc_refresh_tokens_user_idx": { + "name": "oidc_refresh_tokens_user_idx", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "oidc_refresh_tokens_tenant_id_tenants_id_fk": { + "name": "oidc_refresh_tokens_tenant_id_tenants_id_fk", + "tableFrom": "oidc_refresh_tokens", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oidc_refresh_tokens_user_id_users_id_fk": { + "name": "oidc_refresh_tokens_user_id_users_id_fk", + "tableFrom": "oidc_refresh_tokens", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oidc_refresh_tokens_session_id_sessions_id_fk": { + "name": "oidc_refresh_tokens_session_id_sessions_id_fk", + "tableFrom": "oidc_refresh_tokens", + "tableTo": "sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "parts": { + "name": "parts", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "team_id": { + "name": "team_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "leader_id": { + "name": "leader_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'active'" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + } + }, + "indexes": { + "parts_tenant_idx": { + "name": "parts_tenant_idx", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "parts_team_idx": { + "name": "parts_team_idx", + "columns": [ + "team_id" + ], + "isUnique": false + }, + "parts_tenant_code_uidx": { + "name": "parts_tenant_code_uidx", + "columns": [ + "tenant_id", + "code" + ], + "isUnique": true + } + }, + "foreignKeys": { + "parts_tenant_id_tenants_id_fk": { + "name": "parts_tenant_id_tenants_id_fk", + "tableFrom": "parts", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "parts_team_id_teams_id_fk": { + "name": "parts_team_id_teams_id_fk", + "tableFrom": "parts", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "parts_leader_id_users_id_fk": { + "name": "parts_leader_id_users_id_fk", + "tableFrom": "parts", + "tableTo": "users", + "columnsFrom": [ + "leader_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "password_reset_tokens": { + "name": "password_reset_tokens", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "used_at": { + "name": "used_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + } + }, + "indexes": { + "password_reset_tokens_user_idx": { + "name": "password_reset_tokens_user_idx", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "password_reset_tokens_hash_uidx": { + "name": "password_reset_tokens_hash_uidx", + "columns": [ + "token_hash" + ], + "isUnique": true + } + }, + "foreignKeys": { + "password_reset_tokens_user_id_users_id_fk": { + "name": "password_reset_tokens_user_id_users_id_fk", + "tableFrom": "password_reset_tokens", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "positions": { + "name": "positions", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "level": { + "name": "level", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + } + }, + "indexes": { + "positions_tenant_idx": { + "name": "positions_tenant_idx", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "positions_tenant_code_uidx": { + "name": "positions_tenant_code_uidx", + "columns": [ + "tenant_id", + "code" + ], + "isUnique": true + } + }, + "foreignKeys": { + "positions_tenant_id_tenants_id_fk": { + "name": "positions_tenant_id_tenants_id_fk", + "tableFrom": "positions", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "rate_limits": { + "name": "rate_limits", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "count": { + "name": "count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "saml_authn_request_ids": { + "name": "saml_authn_request_ids", + "columns": { + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "request_id": { + "name": "request_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sp_entity_id": { + "name": "sp_entity_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "seen_at": { + "name": "seen_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "saml_authn_request_ids_tenant_req_uidx": { + "name": "saml_authn_request_ids_tenant_req_uidx", + "columns": [ + "tenant_id", + "request_id" + ], + "isUnique": true + }, + "saml_authn_request_ids_expires_idx": { + "name": "saml_authn_request_ids_expires_idx", + "columns": [ + "expires_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "saml_authn_request_ids_tenant_id_tenants_id_fk": { + "name": "saml_authn_request_ids_tenant_id_tenants_id_fk", + "tableFrom": "saml_authn_request_ids", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "saml_sessions": { + "name": "saml_sessions", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sp_id": { + "name": "sp_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "session_index": { + "name": "session_index", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name_id": { + "name": "name_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name_id_format": { + "name": "name_id_format", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "not_on_or_after": { + "name": "not_on_or_after", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + }, + "ended_at": { + "name": "ended_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "saml_sessions_session_index_uidx": { + "name": "saml_sessions_session_index_uidx", + "columns": [ + "session_index" + ], + "isUnique": true + }, + "saml_sessions_tenant_sp_idx": { + "name": "saml_sessions_tenant_sp_idx", + "columns": [ + "tenant_id", + "sp_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "saml_sessions_tenant_id_tenants_id_fk": { + "name": "saml_sessions_tenant_id_tenants_id_fk", + "tableFrom": "saml_sessions", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "saml_sessions_sp_id_saml_sps_id_fk": { + "name": "saml_sessions_sp_id_saml_sps_id_fk", + "tableFrom": "saml_sessions", + "tableTo": "saml_sps", + "columnsFrom": [ + "sp_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "saml_sessions_user_id_users_id_fk": { + "name": "saml_sessions_user_id_users_id_fk", + "tableFrom": "saml_sessions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "saml_sessions_session_id_sessions_id_fk": { + "name": "saml_sessions_session_id_sessions_id_fk", + "tableFrom": "saml_sessions", + "tableTo": "sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "saml_slo_states": { + "name": "saml_slo_states", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "idp_session_record_id": { + "name": "idp_session_record_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "initiating_sp_entity_id": { + "name": "initiating_sp_entity_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "in_response_to": { + "name": "in_response_to", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "initiator_slo_url": { + "name": "initiator_slo_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "completion_uri": { + "name": "completion_uri", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "pending_sp_data_json": { + "name": "pending_sp_data_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "saml_slo_states_tenant_id_tenants_id_fk": { + "name": "saml_slo_states_tenant_id_tenants_id_fk", + "tableFrom": "saml_slo_states", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "saml_slo_states_user_id_users_id_fk": { + "name": "saml_slo_states_user_id_users_id_fk", + "tableFrom": "saml_slo_states", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "saml_sps": { + "name": "saml_sps", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "entity_id": { + "name": "entity_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "acs_url": { + "name": "acs_url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "acs_binding": { + "name": "acs_binding", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST'" + }, + "slo_url": { + "name": "slo_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "slo_binding": { + "name": "slo_binding", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cert": { + "name": "cert", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name_id_format": { + "name": "name_id_format", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress'" + }, + "sign_assertion": { + "name": "sign_assertion", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "sign_response": { + "name": "sign_response", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "encrypt_assertion": { + "name": "encrypt_assertion", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "want_authn_requests_signed": { + "name": "want_authn_requests_signed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "attribute_mapping_json": { + "name": "attribute_mapping_json", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "allowed_attributes": { + "name": "allowed_attributes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enabled": { + "name": "enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + } + }, + "indexes": { + "saml_sps_tenant_entity_id_uidx": { + "name": "saml_sps_tenant_entity_id_uidx", + "columns": [ + "tenant_id", + "entity_id" + ], + "isUnique": true + }, + "saml_sps_tenant_idx": { + "name": "saml_sps_tenant_idx", + "columns": [ + "tenant_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "saml_sps_tenant_id_tenants_id_fk": { + "name": "saml_sps_tenant_id_tenants_id_fk", + "tableFrom": "saml_sps", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "service_roles": { + "name": "service_roles", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "service_type": { + "name": "service_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "service_ref_id": { + "name": "service_ref_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_default": { + "name": "is_default", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "display_order": { + "name": "display_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + } + }, + "indexes": { + "service_roles_service_key_uidx": { + "name": "service_roles_service_key_uidx", + "columns": [ + "service_type", + "service_ref_id", + "key" + ], + "isUnique": true + }, + "service_roles_tenant_service_idx": { + "name": "service_roles_tenant_service_idx", + "columns": [ + "tenant_id", + "service_type", + "service_ref_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "service_roles_tenant_id_tenants_id_fk": { + "name": "service_roles_tenant_id_tenants_id_fk", + "tableFrom": "service_roles", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "sessions": { + "name": "sessions", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "idp_session_id": { + "name": "idp_session_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "amr": { + "name": "amr", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "acr": { + "name": "acr", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ip": { + "name": "ip", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "sessions_idp_session_id_uidx": { + "name": "sessions_idp_session_id_uidx", + "columns": [ + "idp_session_id" + ], + "isUnique": true + }, + "sessions_user_idx": { + "name": "sessions_user_idx", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "sessions_expires_idx": { + "name": "sessions_expires_idx", + "columns": [ + "expires_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "sessions_tenant_id_tenants_id_fk": { + "name": "sessions_tenant_id_tenants_id_fk", + "tableFrom": "sessions", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "sessions_user_id_users_id_fk": { + "name": "sessions_user_id_users_id_fk", + "tableFrom": "sessions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "signing_keys": { + "name": "signing_keys", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "kid": { + "name": "kid", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "use": { + "name": "use", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'sig'" + }, + "alg": { + "name": "alg", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "public_jwk": { + "name": "public_jwk", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "private_jwk_encrypted": { + "name": "private_jwk_encrypted", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "cert_pem": { + "name": "cert_pem", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "active": { + "name": "active", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + }, + "rotated_at": { + "name": "rotated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "not_after": { + "name": "not_after", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "signing_keys_tenant_kid_uidx": { + "name": "signing_keys_tenant_kid_uidx", + "columns": [ + "tenant_id", + "kid" + ], + "isUnique": true + }, + "signing_keys_tenant_active_idx": { + "name": "signing_keys_tenant_active_idx", + "columns": [ + "tenant_id", + "active" + ], + "isUnique": false + }, + "signing_keys_tenant_one_active_uidx": { + "name": "signing_keys_tenant_one_active_uidx", + "columns": [ + "tenant_id" + ], + "isUnique": true, + "where": "active = 1" + } + }, + "foreignKeys": { + "signing_keys_tenant_id_tenants_id_fk": { + "name": "signing_keys_tenant_id_tenants_id_fk", + "tableFrom": "signing_keys", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "teams": { + "name": "teams", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "department_id": { + "name": "department_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "leader_id": { + "name": "leader_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'active'" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + } + }, + "indexes": { + "teams_tenant_idx": { + "name": "teams_tenant_idx", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "teams_department_idx": { + "name": "teams_department_idx", + "columns": [ + "department_id" + ], + "isUnique": false + }, + "teams_tenant_code_uidx": { + "name": "teams_tenant_code_uidx", + "columns": [ + "tenant_id", + "code" + ], + "isUnique": true + } + }, + "foreignKeys": { + "teams_tenant_id_tenants_id_fk": { + "name": "teams_tenant_id_tenants_id_fk", + "tableFrom": "teams", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "teams_department_id_departments_id_fk": { + "name": "teams_department_id_departments_id_fk", + "tableFrom": "teams", + "tableTo": "departments", + "columnsFrom": [ + "department_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "teams_leader_id_users_id_fk": { + "name": "teams_leader_id_users_id_fk", + "tableFrom": "teams", + "tableTo": "users", + "columnsFrom": [ + "leader_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tenants": { + "name": "tenants", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'active'" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + } + }, + "indexes": { + "tenants_slug_uidx": { + "name": "tenants_slug_uidx", + "columns": [ + "slug" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user_departments": { + "name": "user_departments", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "department_id": { + "name": "department_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "position_id": { + "name": "position_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "job_title": { + "name": "job_title", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_primary": { + "name": "is_primary", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "started_at": { + "name": "started_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + }, + "ended_at": { + "name": "ended_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + } + }, + "indexes": { + "user_departments_user_idx": { + "name": "user_departments_user_idx", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "user_departments_dept_idx": { + "name": "user_departments_dept_idx", + "columns": [ + "department_id" + ], + "isUnique": false + }, + "user_departments_tenant_idx": { + "name": "user_departments_tenant_idx", + "columns": [ + "tenant_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "user_departments_tenant_id_tenants_id_fk": { + "name": "user_departments_tenant_id_tenants_id_fk", + "tableFrom": "user_departments", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_departments_user_id_users_id_fk": { + "name": "user_departments_user_id_users_id_fk", + "tableFrom": "user_departments", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_departments_department_id_departments_id_fk": { + "name": "user_departments_department_id_departments_id_fk", + "tableFrom": "user_departments", + "tableTo": "departments", + "columnsFrom": [ + "department_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_departments_position_id_positions_id_fk": { + "name": "user_departments_position_id_positions_id_fk", + "tableFrom": "user_departments", + "tableTo": "positions", + "columnsFrom": [ + "position_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user_parts": { + "name": "user_parts", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "part_id": { + "name": "part_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "job_title": { + "name": "job_title", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_primary": { + "name": "is_primary", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "started_at": { + "name": "started_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + }, + "ended_at": { + "name": "ended_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + } + }, + "indexes": { + "user_parts_user_idx": { + "name": "user_parts_user_idx", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "user_parts_part_idx": { + "name": "user_parts_part_idx", + "columns": [ + "part_id" + ], + "isUnique": false + }, + "user_parts_tenant_idx": { + "name": "user_parts_tenant_idx", + "columns": [ + "tenant_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "user_parts_tenant_id_tenants_id_fk": { + "name": "user_parts_tenant_id_tenants_id_fk", + "tableFrom": "user_parts", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_parts_user_id_users_id_fk": { + "name": "user_parts_user_id_users_id_fk", + "tableFrom": "user_parts", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_parts_part_id_parts_id_fk": { + "name": "user_parts_part_id_parts_id_fk", + "tableFrom": "user_parts", + "tableTo": "parts", + "columnsFrom": [ + "part_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user_service_assignments": { + "name": "user_service_assignments", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "service_type": { + "name": "service_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "service_ref_id": { + "name": "service_ref_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "service_role_id": { + "name": "service_role_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "attributes_json": { + "name": "attributes_json", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "granted_by": { + "name": "granted_by", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "granted_at": { + "name": "granted_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + } + }, + "indexes": { + "user_service_assignments_user_service_uidx": { + "name": "user_service_assignments_user_service_uidx", + "columns": [ + "tenant_id", + "user_id", + "service_type", + "service_ref_id" + ], + "isUnique": true + }, + "user_service_assignments_tenant_user_idx": { + "name": "user_service_assignments_tenant_user_idx", + "columns": [ + "tenant_id", + "user_id" + ], + "isUnique": false + }, + "user_service_assignments_tenant_service_idx": { + "name": "user_service_assignments_tenant_service_idx", + "columns": [ + "tenant_id", + "service_type", + "service_ref_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "user_service_assignments_tenant_id_tenants_id_fk": { + "name": "user_service_assignments_tenant_id_tenants_id_fk", + "tableFrom": "user_service_assignments", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_service_assignments_user_id_users_id_fk": { + "name": "user_service_assignments_user_id_users_id_fk", + "tableFrom": "user_service_assignments", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_service_assignments_service_role_id_service_roles_id_fk": { + "name": "user_service_assignments_service_role_id_service_roles_id_fk", + "tableFrom": "user_service_assignments", + "tableTo": "service_roles", + "columnsFrom": [ + "service_role_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user_teams": { + "name": "user_teams", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "team_id": { + "name": "team_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "job_title": { + "name": "job_title", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_primary": { + "name": "is_primary", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "started_at": { + "name": "started_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + }, + "ended_at": { + "name": "ended_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + } + }, + "indexes": { + "user_teams_user_idx": { + "name": "user_teams_user_idx", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "user_teams_team_idx": { + "name": "user_teams_team_idx", + "columns": [ + "team_id" + ], + "isUnique": false + }, + "user_teams_tenant_idx": { + "name": "user_teams_tenant_idx", + "columns": [ + "tenant_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "user_teams_tenant_id_tenants_id_fk": { + "name": "user_teams_tenant_id_tenants_id_fk", + "tableFrom": "user_teams", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_teams_user_id_users_id_fk": { + "name": "user_teams_user_id_users_id_fk", + "tableFrom": "user_teams", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_teams_team_id_teams_id_fk": { + "name": "user_teams_team_id_teams_id_fk", + "tableFrom": "user_teams", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "users": { + "name": "users", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email_verified_at": { + "name": "email_verified_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'user'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'active'" + }, + "given_name": { + "name": "given_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "family_name": { + "name": "family_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "phone_number": { + "name": "phone_number", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "phone_verified_at": { + "name": "phone_verified_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "avatar_url": { + "name": "avatar_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "locale": { + "name": "locale", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'ko-KR'" + }, + "zoneinfo": { + "name": "zoneinfo", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'Asia/Seoul'" + }, + "bio": { + "name": "bio", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "birthdate": { + "name": "birthdate", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "address_street": { + "name": "address_street", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "address_locality": { + "name": "address_locality", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "address_region": { + "name": "address_region", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "address_postal_code": { + "name": "address_postal_code", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "address_country": { + "name": "address_country", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + } + }, + "indexes": { + "users_tenant_email_uidx": { + "name": "users_tenant_email_uidx", + "columns": [ + "tenant_id", + "email" + ], + "isUnique": true + }, + "users_tenant_username_uidx": { + "name": "users_tenant_username_uidx", + "columns": [ + "tenant_id", + "username" + ], + "isUnique": true + }, + "users_tenant_idx": { + "name": "users_tenant_idx", + "columns": [ + "tenant_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "users_tenant_id_tenants_id_fk": { + "name": "users_tenant_id_tenants_id_fk", + "tableFrom": "users", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "webauthn_challenges": { + "name": "webauthn_challenges", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "challenge": { + "name": "challenge", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "used_at": { + "name": "used_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "webauthn_challenges_tenant_challenge_uidx": { + "name": "webauthn_challenges_tenant_challenge_uidx", + "columns": [ + "tenant_id", + "challenge" + ], + "isUnique": true + }, + "webauthn_challenges_tenant_expires_idx": { + "name": "webauthn_challenges_tenant_expires_idx", + "columns": [ + "tenant_id", + "expires_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "webauthn_challenges_tenant_id_tenants_id_fk": { + "name": "webauthn_challenges_tenant_id_tenants_id_fk", + "tableFrom": "webauthn_challenges", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "webauthn_challenges_user_id_users_id_fk": { + "name": "webauthn_challenges_user_id_users_id_fk", + "tableFrom": "webauthn_challenges", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/drizzle/sqlite/meta/_journal.json b/drizzle/sqlite/meta/_journal.json index 8f214e7..1ede302 100644 --- a/drizzle/sqlite/meta/_journal.json +++ b/drizzle/sqlite/meta/_journal.json @@ -22,6 +22,13 @@ "when": 1783222882602, "tag": "0002_friendly_king_bedlam", "breakpoints": true + }, + { + "idx": 3, + "version": "6", + "when": 1783246073168, + "tag": "0003_white_speed_demon", + "breakpoints": true } ] } \ No newline at end of file diff --git a/src/lib/i18n/ko.json b/src/lib/i18n/ko.json index 9814781..eff4b7b 100644 --- a/src/lib/i18n/ko.json +++ b/src/lib/i18n/ko.json @@ -262,6 +262,11 @@ "bio": "소개", "locale": "언어", "timezone": "시간대", + "address_street": "도로명 주소", + "address_locality": "시/군/구", + "address_region": "시/도", + "address_postal_code": "우편번호", + "address_country": "국가", "role": "역할", "status": "상태", "role_user": "일반 사용자", diff --git a/src/lib/server/db/schema.mysql.ts b/src/lib/server/db/schema.mysql.ts index fd67c79..5d0c507 100644 --- a/src/lib/server/db/schema.mysql.ts +++ b/src/lib/server/db/schema.mysql.ts @@ -55,6 +55,13 @@ export const users = mysqlTable( zoneinfo: text("zoneinfo").default("Asia/Seoul"), bio: text("bio"), birthdate: text("birthdate"), // ISO 8601 날짜 문자열 (YYYY-MM-DD) + // 주소 (OIDC address 클레임 구성요소). formatted 는 저장하지 않고 발급 시 조합. + // text 로 충분 — unique index 를 걸지 않으므로 키 길이 제약 무관. 3방언 parity 유지. + addressStreet: text("address_street"), + addressLocality: text("address_locality"), + addressRegion: text("address_region"), + addressPostalCode: text("address_postal_code"), + addressCountry: text("address_country"), createdAt: datetime("created_at", { mode: "date", fsp: 3 }) .notNull() .default(sql`(CURRENT_TIMESTAMP(3))`), diff --git a/src/lib/server/db/schema.pg.ts b/src/lib/server/db/schema.pg.ts index 2cb1b23..0bdd96c 100644 --- a/src/lib/server/db/schema.pg.ts +++ b/src/lib/server/db/schema.pg.ts @@ -51,6 +51,12 @@ export const users = pgTable( zoneinfo: text("zoneinfo").default("Asia/Seoul"), bio: text("bio"), birthdate: text("birthdate"), // ISO 8601 날짜 문자열 (YYYY-MM-DD) + // 주소 (OIDC address 클레임 구성요소). formatted 는 저장하지 않고 발급 시 조합. + addressStreet: text("address_street"), + addressLocality: text("address_locality"), + addressRegion: text("address_region"), + addressPostalCode: text("address_postal_code"), + addressCountry: text("address_country"), createdAt: timestamp("created_at", { mode: "date", withTimezone: true, precision: 3 }).notNull().defaultNow(), updatedAt: timestamp("updated_at", { mode: "date", withTimezone: true, precision: 3 }).notNull().defaultNow(), }, diff --git a/src/lib/server/db/schema.sqlite.ts b/src/lib/server/db/schema.sqlite.ts index be80573..3a9fc80 100644 --- a/src/lib/server/db/schema.sqlite.ts +++ b/src/lib/server/db/schema.sqlite.ts @@ -55,6 +55,12 @@ export const users = sqliteTable( zoneinfo: text("zoneinfo").default("Asia/Seoul"), bio: text("bio"), birthdate: text("birthdate"), // ISO 8601 날짜 문자열 (YYYY-MM-DD) + // 주소 (OIDC address 클레임 구성요소). formatted 는 저장하지 않고 발급 시 조합. + addressStreet: text("address_street"), + addressLocality: text("address_locality"), + addressRegion: text("address_region"), + addressPostalCode: text("address_postal_code"), + addressCountry: text("address_country"), createdAt: integer("created_at", { mode: "timestamp_ms" }) .notNull() .default(sql`(unixepoch() * 1000)`), diff --git a/src/lib/server/oidc/claims.ts b/src/lib/server/oidc/claims.ts new file mode 100644 index 0000000..3f44531 --- /dev/null +++ b/src/lib/server/oidc/claims.ts @@ -0,0 +1,45 @@ +/** + * OIDC 표준 클레임 구성 헬퍼. + * token(id_token) 과 userinfo 응답이 동일 로직을 공유하도록 이곳에 모은다. + */ + +/** users 테이블의 주소 컬럼 부분집합. */ +export interface AddressClaimSource { + addressStreet: string | null; + addressLocality: string | null; + addressRegion: string | null; + addressPostalCode: string | null; + addressCountry: string | null; +} + +/** + * OIDC 표준 `address` 클레임(JSON object)을 구성한다. + * https://openid.net/specs/openid-connect-core-1_0.html#AddressClaim + * + * - street_address / locality / region / postal_code / country 는 각 컬럼을 그대로 매핑한다. + * - formatted 는 저장하지 않고 존재하는 구성요소만 조합한다(빈 값 제외). + * - 모든 하위 필드가 비어 있으면 null 을 반환한다(빈 object 발급 금지). + */ +export function buildAddressClaim(source: AddressClaimSource): Record | null { + const street = source.addressStreet?.trim() || null; + const locality = source.addressLocality?.trim() || null; + const region = source.addressRegion?.trim() || null; + const postalCode = source.addressPostalCode?.trim() || null; + const country = source.addressCountry?.trim() || null; + + if (!street && !locality && !region && !postalCode && !country) return null; + + const claim: Record = {}; + if (street) claim.street_address = street; + if (locality) claim.locality = locality; + if (region) claim.region = region; + if (postalCode) claim.postal_code = postalCode; + if (country) claim.country = country; + + // formatted: street_address 는 별도 줄, locality/region/postal_code 는 한 줄로, country 는 마지막 줄. + const cityLine = [locality, region, postalCode].filter(Boolean).join(" "); + const lines = [street, cityLine || null, country].filter((v): v is string => Boolean(v)); + if (lines.length > 0) claim.formatted = lines.join("\n"); + + return claim; +} diff --git a/src/routes/.well-known/openid-configuration/+server.ts b/src/routes/.well-known/openid-configuration/+server.ts index d18c98d..26e74bb 100644 --- a/src/routes/.well-known/openid-configuration/+server.ts +++ b/src/routes/.well-known/openid-configuration/+server.ts @@ -17,7 +17,7 @@ export const GET: RequestHandler = async ({ locals, url }) => { revocation_endpoint: `${issuer}/oidc/revoke`, introspection_endpoint_auth_methods_supported: ["client_secret_basic", "client_secret_post"], revocation_endpoint_auth_methods_supported: ["client_secret_basic", "client_secret_post"], - scopes_supported: ["openid", "profile", "email", "phone", "organization", "groups", "offline_access"], + scopes_supported: ["openid", "profile", "email", "phone", "address", "organization", "groups", "offline_access"], response_types_supported: ["code"], grant_types_supported: ["authorization_code", "refresh_token"], subject_types_supported: ["public"], @@ -50,6 +50,7 @@ export const GET: RequestHandler = async ({ locals, url }) => { "updated_at", "phone_number", "phone_number_verified", + "address", "department", "team", "position", diff --git a/src/routes/admin/users/[id]/+page.server.ts b/src/routes/admin/users/[id]/+page.server.ts index 641156d..75fea81 100644 --- a/src/routes/admin/users/[id]/+page.server.ts +++ b/src/routes/admin/users/[id]/+page.server.ts @@ -223,6 +223,12 @@ export const actions: Actions = { const birthdate = String(fd.get("birthdate") ?? "").trim() || null; const locale = String(fd.get("locale") ?? "ko-KR").trim(); const zoneinfo = String(fd.get("zoneinfo") ?? "Asia/Seoul").trim(); + // 주소 (OIDC address 클레임 구성요소). 빈 값은 null 로 저장. + const addressStreet = String(fd.get("addressStreet") ?? "").trim() || null; + const addressLocality = String(fd.get("addressLocality") ?? "").trim() || null; + const addressRegion = String(fd.get("addressRegion") ?? "").trim() || null; + const addressPostalCode = String(fd.get("addressPostalCode") ?? "").trim() || null; + const addressCountry = String(fd.get("addressCountry") ?? "").trim() || null; await db .update(users) @@ -235,6 +241,11 @@ export const actions: Actions = { birthdate, locale, zoneinfo, + addressStreet, + addressLocality, + addressRegion, + addressPostalCode, + addressCountry, role: effectiveRole, status: effectiveStatus, updatedAt: new Date(), diff --git a/src/routes/admin/users/[id]/+page.svelte b/src/routes/admin/users/[id]/+page.svelte index b60b8d4..7f7a654 100644 --- a/src/routes/admin/users/[id]/+page.svelte +++ b/src/routes/admin/users/[id]/+page.svelte @@ -135,6 +135,51 @@ const TIMEZONE_OPTIONS = [ {/each} +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
` : ""; + const html = + `SSO 리다이렉트 중...` + + `` + + `
` + + `${relayStateInput}` + + `
`; + return new Response(html, { headers: { "Content-Type": "text/html; charset=utf-8" } }); +} + +/** 서명된 SAML 오류 Response 를 만들어 ACS 로 POST 하는 폼 응답. */ +async function buildAndRenderSamlError(params: { + inResponseTo: string | null; + acsUrl: string; + issuerUrl: string; + subStatusCode: string; + certPem: string; + privateKey: CryptoKey; + relayState: string | null; +}): Promise { + const errorB64 = await buildSignedSamlErrorResponse({ + inResponseTo: params.inResponseTo ?? "", + acsUrl: params.acsUrl, + issuerUrl: params.issuerUrl, + subStatusCode: params.subStatusCode, + certPem: params.certPem, + privateKey: params.privateKey, + }); + return renderAutoSubmitForm(params.acsUrl, errorB64, params.relayState); +} + +interface GateAndIssueParams { + db: DB; + tenant: Tenant; + issuerUrl: string; + sp: SamlSpRecord; + user: User; + session: Session; + acsUrl: string; + /** SP-initiated 면 AuthnRequest ID, IdP-initiated(unsolicited) 면 null. */ + inResponseTo: string | null; + relayState: string | null; + certPem: string; + privateKey: CryptoKey; +} - // Destination 검증: SP 가 명시했으면 IdP 의 SSO endpoint 와 정확히 일치해야 한다. - if (authnRequest.destination) { - const expectedDestination = `${config.issuerUrl.replace(/\/+$/, "")}/saml/sso`; - if (authnRequest.destination !== expectedDestination) { - throw error(400, "AuthnRequest Destination 이 IdP 의 SSO endpoint 와 일치하지 않습니다."); - } - } +/** + * 공통 후반부: 서비스 권한 게이트 → (SP-initiated 한정) replay ID 소비 → attribute 매핑 → + * NameID 결정 → SAML 세션 기록 → Response 서명/암호화 → ACS POST 폼 렌더. + * + * SP-initiated / IdP-initiated 세 흐름이 모두 재사용한다. inResponseTo 가 있으면 그 값을 + * Response 의 InResponseTo 로 채우고 replay ID 를 소비하며, null 이면 unsolicited 로 처리한다. + * + * replay ID 소비는 "Assertion 발급 직전"에 수행한다 — 로그인/forceAuthn 재진입으로 동일 + * AuthnRequest 가 되돌아오는 정상 흐름을 깨지 않으면서도, 하나의 AuthnRequest 로 두 번 + * Assertion 이 발급되는 것을 막는다. + */ +async function gateAndIssueSamlAssertion(event: Parameters[0], p: GateAndIssueParams): Promise { + const { db, tenant, sp, user, session } = p; - const sp = await findSp(db, tenant.id, authnRequest.issuer); - if (!sp) { - throw error(403, `등록되지 않은 SP 입니다: ${authnRequest.issuer}`); + // 서비스 권한 게이트 (기본 deny). 매핑 없으면 SSO 거부. + const spAssignment = await getActiveAssignment(db, { + tenantId: tenant.id, + userId: user.id, + serviceType: "saml", + serviceRefId: sp.id, + }); + if (!spAssignment) { + const meta = getRequestMetadata(event); + await recordAuditEvent(db, { + tenantId: tenant.id, + userId: user.id, + actorId: user.id, + spOrClientId: sp.entityId, + kind: "saml_sso", + outcome: "failure", + ip: meta.ip, + userAgent: meta.userAgent, + detail: { error: "access_denied", reason: "no_service_assignment" }, + }); + throw error(403, "이 SP에 대한 권한이 없습니다."); } - // Replay 가드: 동일 AuthnRequest ID 가 이미 사용된 적이 있는지 확인 후 INSERT. - // expiresAt 이 지난 행은 무시 (clean-up 은 별도 job). - if (authnRequest.id) { + // Replay 가드 (SP-initiated 한정). Assertion 발급 직전에 동일 AuthnRequest ID 의 + // 재사용 여부를 확인 후 INSERT. unsolicited(inResponseTo=null)는 대응 요청이 없어 생략. + if (p.inResponseTo) { const now = new Date(); const [seen] = await db .select({ requestId: samlAuthnRequestIds.requestId }) .from(samlAuthnRequestIds) - .where(and(eq(samlAuthnRequestIds.tenantId, tenant.id), eq(samlAuthnRequestIds.requestId, authnRequest.id), gt(samlAuthnRequestIds.expiresAt, now))) + .where(and(eq(samlAuthnRequestIds.tenantId, tenant.id), eq(samlAuthnRequestIds.requestId, p.inResponseTo), gt(samlAuthnRequestIds.expiresAt, now))) .limit(1); if (seen) { throw error(400, "AuthnRequest ID 가 이미 사용되었습니다 (replay)"); @@ -81,8 +138,8 @@ export const GET: RequestHandler = async (event) => { try { await db.insert(samlAuthnRequestIds).values({ tenantId: tenant.id, - requestId: authnRequest.id, - spEntityId: authnRequest.issuer, + requestId: p.inResponseTo, + spEntityId: sp.entityId, expiresAt: new Date(Date.now() + SAML_AUTHN_REQUEST_TTL_MS), }); } catch { @@ -91,139 +148,6 @@ export const GET: RequestHandler = async (event) => { } } - // AuthnRequest 서명 검증: SP 가 서명을 요구하거나 Signature 파라미터가 있는 경우 - const hasSig = url.searchParams.has("Signature"); - if (sp.wantAuthnRequestsSigned || hasSig) { - if (!sp.cert) { - throw error(400, "SP 인증서가 등록되지 않아 AuthnRequest 서명을 검증할 수 없습니다."); - } - const rawQuery = url.search.slice(1); - const sigValid = await verifySamlRedirectSignature(rawQuery, sp.cert); - if (!sigValid) { - throw error(400, "AuthnRequest 서명 검증에 실패했습니다."); - } - } - - // ACS URL: AuthnRequest 에 명시된 경우 반드시 등록된 SP의 ACS URL 과 일치해야 한다. - // 다른 URL 을 허용하면 공격자가 서명된 Assertion 을 자신의 서버로 가로챌 수 있다. - if (authnRequest.acsUrl && authnRequest.acsUrl !== sp.acsUrl) { - throw error(400, "AuthnRequest의 ACS URL이 등록된 SP ACS URL과 일치하지 않습니다."); - } - const acsUrl = sp.acsUrl; - - const signingKey = await getActiveSigningKey(db, tenant.id, config.signingKeySecret); - if (!signingKey || !signingKey.certPem) { - throw error(503, "서명 키가 없습니다. 서버를 재시작하여 키를 생성하세요."); - } - - // isPassive: 사용자 인터랙션 없이 처리해야 하므로, 세션이 없으면 NoPassive 오류를 ACS 로 반환. - if (authnRequest.isPassive && (!locals.user || !locals.session)) { - const errorB64 = await buildSignedSamlErrorResponse({ - inResponseTo: authnRequest.id, - acsUrl, - issuerUrl: config.issuerUrl, - subStatusCode: "urn:oasis:names:tc:SAML:2.0:status:NoPassive", - certPem: signingKey.certPem, - privateKey: signingKey.privateKey, - }); - const relayStateInput = authnRequest.relayState ? `` : ""; - return new Response( - `SSO 리다이렉트 중...` + - `` + - `
` + - `${relayStateInput}` + - `
`, - { headers: { "Content-Type": "text/html; charset=utf-8" } }, - ); - } - - // 로그인 여부 확인 → 미로그인 시 로그인 페이지로 - if (!locals.user || !locals.session) { - const loginUrl = new URL("/login", url); - loginUrl.searchParams.set("redirectTo", url.pathname + url.search); - loginUrl.searchParams.set("skinHint", `saml:${sp.id}`); - throw redirect(302, loginUrl.toString()); - } - - // forceAuthn: SP 가 강제 재인증을 요구하면, 현재 세션 상태와 무관하게 /login 으로 보낸다. - // 무한 루프 방지: AuthnRequest ID 를 쿠키에 기록해 두고, 동일 요청에 대한 재진입이면 통과시킨다. - if (authnRequest.forceAuthn) { - const reauthCookieName = `saml_reauth_${authnRequest.id}`; - const alreadyReauthed = event.cookies.get(reauthCookieName) === "1"; - if (!alreadyReauthed) { - // 다음 요청에서 동일 AuthnRequest 가 들어오면 통과되도록 짧은 TTL 쿠키를 설정. - event.cookies.set(reauthCookieName, "1", { - path: "/saml/sso", - httpOnly: true, - sameSite: "lax", - secure: url.protocol === "https:", - maxAge: 600, - }); - const loginUrl = new URL("/login", url); - loginUrl.searchParams.set("redirectTo", url.pathname + url.search); - loginUrl.searchParams.set("skinHint", `saml:${sp.id}`); - loginUrl.searchParams.set("forceAuthn", "true"); - throw redirect(302, loginUrl.toString()); - } - // 이미 재인증을 거치고 돌아온 경우 — 쿠키 삭제 후 SSO 응답 진행 - event.cookies.delete(reauthCookieName, { path: "/saml/sso" }); - } - - // RequestedAuthnContext: 세션 ACR 이 SP 요구 수준을 만족하는지 검사한다. - if (authnRequest.requestedAuthnContext && !acrSatisfies(locals.session.acr, authnRequest.requestedAuthnContext)) { - // 세션이 issueInstant 이후에 생성됐다면 재인증을 이미 거쳤으나 ACR 이 여전히 부족한 것. - // (예: MFA 미설정 사용자가 refeds/mfa 를 요구받은 경우) → NoAuthnContext 오류 반환. - const isPostReauth = locals.session.createdAt >= authnRequest.issueInstant; - if (isPostReauth || authnRequest.isPassive) { - const errorB64 = await buildSignedSamlErrorResponse({ - inResponseTo: authnRequest.id, - acsUrl, - issuerUrl: config.issuerUrl, - subStatusCode: "urn:oasis:names:tc:SAML:2.0:status:NoAuthnContext", - certPem: signingKey.certPem, - privateKey: signingKey.privateKey, - }); - const relayStateInput = authnRequest.relayState ? `` : ""; - return new Response( - `SSO 리다이렉트 중...` + - `` + - `
` + - `${relayStateInput}` + - `
`, - { headers: { "Content-Type": "text/html; charset=utf-8" } }, - ); - } - // 첫 시도: 재인증(MFA 포함)을 강제한다. - const loginUrl = new URL("/login", url); - loginUrl.searchParams.set("redirectTo", url.pathname + url.search); - loginUrl.searchParams.set("skinHint", `saml:${sp.id}`); - loginUrl.searchParams.set("forceAuthn", "true"); - throw redirect(302, loginUrl.toString()); - } - - // 서비스 권한 게이트 (기본 deny). 매핑 없으면 SSO 거부. - const spAssignment = await getActiveAssignment(db, { - tenantId: tenant.id, - userId: locals.user.id, - serviceType: "saml", - serviceRefId: sp.id, - }); - if (!spAssignment) { - const meta = getRequestMetadata(event); - await recordAuditEvent(db, { - tenantId: tenant.id, - userId: locals.user.id, - actorId: locals.user.id, - spOrClientId: sp.entityId, - kind: "saml_sso", - outcome: "failure", - ip: meta.ip, - userAgent: meta.userAgent, - detail: { error: "access_denied", reason: "no_service_assignment" }, - }); - throw error(403, "이 SP에 대한 권한이 없습니다."); - } - // Attribute 매핑 (attributeMappingJson 또는 기본값) type AttributeMap = Record; let attrMapping: AttributeMap = {}; @@ -249,7 +173,6 @@ export const GET: RequestHandler = async (event) => { allowedSet = new Set(DEFAULT_ALLOWED); } - const user = locals.user; const attributes: Record = {}; const setAttr = (key: string, value: string | null | undefined) => { if (!value) return; @@ -305,24 +228,24 @@ export const GET: RequestHandler = async (event) => { tenantId: tenant.id, spId: sp.id, userId: user.id, - sessionId: locals.session.id, + sessionId: session.id, sessionIndex, nameId, nameIdFormat, }); const samlResponseB64 = await buildSignedSamlResponse({ - inResponseTo: authnRequest.id, - acsUrl, - issuerUrl: config.issuerUrl, + inResponseTo: p.inResponseTo, // null 이면 unsolicited — InResponseTo 생략 + acsUrl: p.acsUrl, + issuerUrl: p.issuerUrl, spEntityId: sp.entityId, - authnContextClassRef: locals.session.acr ?? undefined, + authnContextClassRef: session.acr ?? undefined, nameId, nameIdFormat, sessionIndex, attributes, - certPem: signingKey.certPem, - privateKey: signingKey.privateKey, + certPem: p.certPem, + privateKey: p.privateKey, signResponse: sp.signResponse, encryptAssertion: sp.encryptAssertion, spCertPem: sp.cert, @@ -338,28 +261,344 @@ export const GET: RequestHandler = async (event) => { outcome: "success", ip: requestMetadata.ip, userAgent: requestMetadata.userAgent, - detail: { spEntityId: sp.entityId, nameId }, + detail: { spEntityId: sp.entityId, nameId, initiatedBy: p.inResponseTo ? "sp" : "idp" }, }); - // HTTP-POST 바인딩: auto-submit 폼 렌더링 - function htmlEscape(s: string): string { - return s.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """).replace(/'/g, "'"); + return renderAutoSubmitForm(p.acsUrl, samlResponseB64, p.relayState); +} + +interface ProcessAuthnRequestParams { + db: DB; + tenant: Tenant; + issuerUrl: string; + sp: SamlSpRecord; + authnRequest: ParsedAuthnRequest; + acsUrl: string; + certPem: string; + privateKey: CryptoKey; + /** + * 미로그인/재인증 시 /login 으로 넘길 redirectTo (path+query). Redirect 바인딩은 현재 + * URL 그대로, POST 바인딩은 동일 AuthnRequest 를 Redirect 바인딩으로 재인코딩한 resume URL. + */ + loginRedirectTo: string; +} + +/** + * SP-initiated AuthnRequest 공통 처리부 (Redirect / POST 바인딩 공유). + * isPassive → 로그인 → forceAuthn → RequestedAuthnContext(ACR) → 게이트 → Response 발급. + * 파싱·서명검증·바인딩별 redirectTo 만 호출부에서 다르게 준비해 넘긴다. + */ +async function processSpInitiatedAuthnRequest(event: Parameters[0], p: ProcessAuthnRequestParams): Promise { + const { locals, url } = event; + const { authnRequest, sp, acsUrl } = p; + + // isPassive: 사용자 인터랙션 없이 처리해야 하므로, 세션이 없으면 NoPassive 오류를 ACS 로 반환. + if (authnRequest.isPassive && (!locals.user || !locals.session)) { + return await buildAndRenderSamlError({ + inResponseTo: authnRequest.id, + acsUrl, + issuerUrl: p.issuerUrl, + subStatusCode: "urn:oasis:names:tc:SAML:2.0:status:NoPassive", + certPem: p.certPem, + privateKey: p.privateKey, + relayState: authnRequest.relayState, + }); } - const relayStateInput = relayState ? `` : ""; + // 로그인 여부 확인 → 미로그인 시 로그인 페이지로 + if (!locals.user || !locals.session) { + const loginUrl = new URL("/login", url); + loginUrl.searchParams.set("redirectTo", p.loginRedirectTo); + loginUrl.searchParams.set("skinHint", `saml:${sp.id}`); + throw redirect(302, loginUrl.toString()); + } + + // forceAuthn: SP 가 강제 재인증을 요구하면, 현재 세션 상태와 무관하게 /login 으로 보낸다. + // 무한 루프 방지: AuthnRequest ID 를 쿠키에 기록해 두고, 동일 요청에 대한 재진입이면 통과시킨다. + if (authnRequest.forceAuthn) { + const reauthCookieName = `saml_reauth_${authnRequest.id}`; + const alreadyReauthed = event.cookies.get(reauthCookieName) === "1"; + if (!alreadyReauthed) { + // 다음 요청에서 동일 AuthnRequest 가 들어오면 통과되도록 짧은 TTL 쿠키를 설정. + event.cookies.set(reauthCookieName, "1", { + path: "/saml/sso", + httpOnly: true, + sameSite: "lax", + secure: url.protocol === "https:", + maxAge: 600, + }); + const loginUrl = new URL("/login", url); + loginUrl.searchParams.set("redirectTo", p.loginRedirectTo); + loginUrl.searchParams.set("skinHint", `saml:${sp.id}`); + loginUrl.searchParams.set("forceAuthn", "true"); + throw redirect(302, loginUrl.toString()); + } + // 이미 재인증을 거치고 돌아온 경우 — 쿠키 삭제 후 SSO 응답 진행 + event.cookies.delete(reauthCookieName, { path: "/saml/sso" }); + } + + // RequestedAuthnContext: 세션 ACR 이 SP 요구 수준을 만족하는지 검사한다. + if (authnRequest.requestedAuthnContext && !acrSatisfies(locals.session.acr, authnRequest.requestedAuthnContext)) { + // 세션이 issueInstant 이후에 생성됐다면 재인증을 이미 거쳤으나 ACR 이 여전히 부족한 것. + // (예: MFA 미설정 사용자가 refeds/mfa 를 요구받은 경우) → NoAuthnContext 오류 반환. + const isPostReauth = locals.session.createdAt >= authnRequest.issueInstant; + if (isPostReauth || authnRequest.isPassive) { + return await buildAndRenderSamlError({ + inResponseTo: authnRequest.id, + acsUrl, + issuerUrl: p.issuerUrl, + subStatusCode: "urn:oasis:names:tc:SAML:2.0:status:NoAuthnContext", + certPem: p.certPem, + privateKey: p.privateKey, + relayState: authnRequest.relayState, + }); + } + // 첫 시도: 재인증(MFA 포함)을 강제한다. + const loginUrl = new URL("/login", url); + loginUrl.searchParams.set("redirectTo", p.loginRedirectTo); + loginUrl.searchParams.set("skinHint", `saml:${sp.id}`); + loginUrl.searchParams.set("forceAuthn", "true"); + throw redirect(302, loginUrl.toString()); + } + + return await gateAndIssueSamlAssertion(event, { + db: p.db, + tenant: p.tenant, + issuerUrl: p.issuerUrl, + sp, + user: locals.user, + session: locals.session, + acsUrl, + inResponseTo: authnRequest.id, + relayState: authnRequest.relayState, + certPem: p.certPem, + privateKey: p.privateKey, + }); +} + +/** + * IdP-initiated (unsolicited) SSO. + * 로그인된 사용자가 `?sp=` 로 SP 를 지정하면, 대응되는 AuthnRequest 없이 + * IdP 가 먼저 Assertion 을 SP 의 등록된 ACS 로 밀어 준다. InResponseTo 없음. + */ +async function handleIdpInitiated(event: Parameters[0], ctx: { db: DB; tenant: Tenant; issuerUrl: string; signingKeySecret: string; spEntityId: string }): Promise { + const { locals, url } = event; + const { db, tenant } = ctx; + + const sp = await findSp(db, tenant.id, ctx.spEntityId); + if (!sp) { + throw error(403, `등록되지 않은 SP 입니다: ${ctx.spEntityId}`); + } + + // 미로그인 시 로그인 페이지로 (로그인 후 동일 IdP-initiated URL 로 복귀) + if (!locals.user || !locals.session) { + const loginUrl = new URL("/login", url); + loginUrl.searchParams.set("redirectTo", url.pathname + url.search); + loginUrl.searchParams.set("skinHint", `saml:${sp.id}`); + throw redirect(302, loginUrl.toString()); + } + + const signingKey = await getActiveSigningKey(db, tenant.id, ctx.signingKeySecret); + if (!signingKey || !signingKey.certPem) { + throw error(503, "서명 키가 없습니다. 서버를 재시작하여 키를 생성하세요."); + } + + const relayState = url.searchParams.get("RelayState"); + + return await gateAndIssueSamlAssertion(event, { + db, + tenant, + issuerUrl: ctx.issuerUrl, + sp, + user: locals.user, + session: locals.session, + acsUrl: sp.acsUrl, // 요청에 ACS 가 없으므로 등록된 SP ACS 사용 + inResponseTo: null, // unsolicited — InResponseTo 생략 + relayState, + certPem: signingKey.certPem, + privateKey: signingKey.privateKey, + }); +} + +/** + * 공통 진입부: rate-limit + config 검증. 통과 시 { db, tenant, config } 반환. + * GET/POST 모두 동일한 IP당 30회/분 제한을 적용한다 (AuthnRequest 파싱·서명 검증 DoS 방지). + */ +async function ssoPreflight(event: Parameters[0]) { + const { locals, platform } = event; + const { db, tenant } = requireDbContext(locals); + const config = getRuntimeConfig(platform); + + const { ipKey } = getRequestMetadata(event); + const rl = await checkRateLimit(db, `saml-sso:${ipKey}`, { windowMs: 60 * 1000, limit: 30 }); + if (!rl.allowed) { + throw error(429, "요청이 너무 많습니다. 잠시 후 다시 시도해 주세요."); + } + + if (!config.issuerUrl) throw error(503, "IDP_ISSUER_URL 미설정"); + if (!config.signingKeySecret) throw error(503, "IDP_SIGNING_KEY_SECRET 미설정"); + + return { db, tenant, issuerUrl: config.issuerUrl, signingKeySecret: config.signingKeySecret }; +} - const html = ` - -SSO 리다이렉트 중... - -
- - ${relayStateInput} -
- -`; - - return new Response(html, { - headers: { "Content-Type": "text/html; charset=utf-8" }, +/** AuthnRequest Destination 이 IdP SSO endpoint 와 일치하는지 검증 (명시된 경우만). */ +function assertDestination(authnRequest: ParsedAuthnRequest, issuerUrl: string): void { + if (authnRequest.destination) { + const expectedDestination = `${issuerUrl.replace(/\/+$/, "")}/saml/sso`; + if (authnRequest.destination !== expectedDestination) { + throw error(400, "AuthnRequest Destination 이 IdP 의 SSO endpoint 와 일치하지 않습니다."); + } + } +} + +/** AuthnRequest 의 ACS URL 이 등록된 SP ACS 와 일치하는지 검증하고 최종 ACS 를 반환. */ +function resolveAcsUrl(authnRequest: ParsedAuthnRequest, sp: SamlSpRecord): string { + // AuthnRequest 에 ACS 가 명시된 경우 반드시 등록된 SP ACS 와 일치해야 한다. + // 다른 URL 을 허용하면 공격자가 서명된 Assertion 을 자신의 서버로 가로챌 수 있다. + if (authnRequest.acsUrl && authnRequest.acsUrl !== sp.acsUrl) { + throw error(400, "AuthnRequest의 ACS URL이 등록된 SP ACS URL과 일치하지 않습니다."); + } + return sp.acsUrl; +} + +/** + * GET /saml/sso + * - SAMLRequest 있음 → SP-initiated / HTTP-Redirect 바인딩 + * - SAMLRequest 없고 sp 있음 → IdP-initiated (unsolicited) + */ +export const GET: RequestHandler = async (event) => { + const { url } = event; + const { db, tenant, issuerUrl, signingKeySecret } = await ssoPreflight(event); + + const samlRequestB64 = url.searchParams.get("SAMLRequest"); + const relayState = url.searchParams.get("RelayState"); + + // ── IdP-initiated 분기: SAMLRequest 없이 sp 파라미터만 존재 ───────────────── + if (!samlRequestB64) { + const spParam = url.searchParams.get("sp"); + if (spParam) { + return await handleIdpInitiated(event, { db, tenant, issuerUrl, signingKeySecret, spEntityId: spParam }); + } + throw error(400, "SAMLRequest 파라미터가 없습니다."); + } + + // ── SP-initiated / HTTP-Redirect 바인딩 ──────────────────────────────────── + let authnRequest: ParsedAuthnRequest; + try { + authnRequest = await parseAuthnRequest(samlRequestB64, relayState); + } catch { + throw error(400, "SAMLRequest 파싱 실패"); + } + + assertDestination(authnRequest, issuerUrl); + + const sp = await findSp(db, tenant.id, authnRequest.issuer); + if (!sp) { + throw error(403, `등록되지 않은 SP 입니다: ${authnRequest.issuer}`); + } + + // AuthnRequest 서명 검증: SP 가 서명을 요구하거나 Signature 파라미터가 있는 경우. + // HTTP-Redirect 바인딩 서명은 URL 쿼리(SAMLRequest&RelayState&SigAlg) 에 대한 detached 서명. + const hasSig = url.searchParams.has("Signature"); + if (sp.wantAuthnRequestsSigned || hasSig) { + if (!sp.cert) { + throw error(400, "SP 인증서가 등록되지 않아 AuthnRequest 서명을 검증할 수 없습니다."); + } + const rawQuery = url.search.slice(1); + const sigValid = await verifySamlRedirectSignature(rawQuery, sp.cert); + if (!sigValid) { + throw error(400, "AuthnRequest 서명 검증에 실패했습니다."); + } + } + + const acsUrl = resolveAcsUrl(authnRequest, sp); + + const signingKey = await getActiveSigningKey(db, tenant.id, signingKeySecret); + if (!signingKey || !signingKey.certPem) { + throw error(503, "서명 키가 없습니다. 서버를 재시작하여 키를 생성하세요."); + } + + return await processSpInitiatedAuthnRequest(event, { + db, + tenant, + issuerUrl, + sp, + authnRequest, + acsUrl, + certPem: signingKey.certPem, + privateKey: signingKey.privateKey, + loginRedirectTo: url.pathname + url.search, + }); +}; + +/** + * POST /saml/sso — SP-initiated / HTTP-POST 바인딩. + * body: SAMLRequest= (deflate 없음), RelayState=... + */ +export const POST: RequestHandler = async (event) => { + const { url } = event; + const { db, tenant, issuerUrl, signingKeySecret } = await ssoPreflight(event); + + const form = await event.request.formData(); + const samlRequestB64 = typeof form.get("SAMLRequest") === "string" ? (form.get("SAMLRequest") as string) : null; + const relayState = typeof form.get("RelayState") === "string" ? (form.get("RelayState") as string) : null; + + if (!samlRequestB64) { + throw error(400, "SAMLRequest 파라미터가 없습니다."); + } + + // HTTP-POST 바인딩: base64(XML), deflate 없음. + let authnRequest: ParsedAuthnRequest; + try { + authnRequest = await parseAuthnRequestPost(samlRequestB64, relayState); + } catch { + throw error(400, "SAMLRequest 파싱 실패"); + } + + assertDestination(authnRequest, issuerUrl); + + const sp = await findSp(db, tenant.id, authnRequest.issuer); + if (!sp) { + throw error(403, `등록되지 않은 SP 입니다: ${authnRequest.issuer}`); + } + + // ── 서명 검증 (HTTP-POST 바인딩) ─────────────────────────────────────────── + // POST 바인딩의 서명 AuthnRequest 는 URL 쿼리 서명이 아니라 요청 XML 내부의 enveloped + // XML 서명(ds:Signature)이다. 현재 코드베이스에는 enveloped XML 서명을 "검증"하는 + // 구현이 없다 (xmldsigjs 는 Response/Assertion 서명 "생성"에만 사용). 검증할 수 없는 + // 서명을 통과시키면 위조된 AuthnRequest 를 수용하게 되므로, 서명이 요구되거나 존재하면 + // 명시적으로 거부한다. (후속 PR: enveloped XML 서명 검증기 도입 후 이 분기 대체.) + if (sp.wantAuthnRequestsSigned || authnRequest.hasSignature) { + throw error(400, "POST 바인딩 서명 AuthnRequest 검증은 아직 지원되지 않습니다. (enveloped XML 서명 검증 미구현 — 후속)"); + } + + const acsUrl = resolveAcsUrl(authnRequest, sp); + + const signingKey = await getActiveSigningKey(db, tenant.id, signingKeySecret); + if (!signingKey || !signingKey.certPem) { + throw error(503, "서명 키가 없습니다. 서버를 재시작하여 키를 생성하세요."); + } + + // 로그인/재인증 후 복귀 URL: POST body 는 GET 리다이렉트로 보존되지 않으므로, 동일 (미서명) + // AuthnRequest 를 HTTP-Redirect 바인딩으로 재인코딩해 기존 GET 경로가 그대로 재개하도록 한다. + const raw = atob(samlRequestB64); + const bin = new Uint8Array(raw.length); + for (let i = 0; i < raw.length; i++) bin[i] = raw.charCodeAt(i); + const xml = new TextDecoder().decode(bin); + const resumeParams = new URLSearchParams(); + resumeParams.set("SAMLRequest", await encodeRedirectBindingSamlRequest(xml)); + if (relayState) resumeParams.set("RelayState", relayState); + const loginRedirectTo = `${url.pathname}?${resumeParams.toString()}`; + + return await processSpInitiatedAuthnRequest(event, { + db, + tenant, + issuerUrl, + sp, + authnRequest, + acsUrl, + certPem: signingKey.certPem, + privateKey: signingKey.privateKey, + loginRedirectTo, }); }; From f2ad873bad8a5abeba82e733b46b07ee639d94f3 Mon Sep 17 00:00:00 2001 From: Henry Jang Date: Sun, 5 Jul 2026 19:28:17 +0900 Subject: [PATCH 6/8] =?UTF-8?q?refactor(admin):=20SAML/LDAP=20URL=C2=B7hos?= =?UTF-8?q?t=20=EA=B2=80=EC=A6=9D=20=EA=B3=B5=ED=86=B5=20=EB=AA=A8?= =?UTF-8?q?=EB=93=88=EB=A1=9C=20=EC=B6=94=EC=B6=9C=20(SSRF=20=ED=94=84?= =?UTF-8?q?=EB=A6=AC=EB=AF=B8=ED=8B=B0=EB=B8=8C=20=ED=86=B5=ED=95=A9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - validation.ts 에 validateSamlUrl, validateLdapHost, validateLdapPort, isCloudMetadataHost, isLinkLocalHost 추가. ldap-providers/skins 에 중복되던 link-local(169.254/16) 정규식 통합. - saml-sps/ldap-providers 는 CRUD 팩토리 미적용(의도적): UI 성공 계약({create:true}) 불일치, before/after diff audit(H-SAML-4)·bindPassword 암호화(H-ADMIN-4)가 팩토리 훅으로 표현 불가 → 강제 적용 시 회귀 확정이라 검증 함수 중복 제거만 수행. 각 라우트 고유 보안 검증 전부 보존. svelte-check 0 errors, vitest 54 passed, build 성공. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/lib/server/validation.ts | 68 +++++++++++++++++++ .../admin/ldap-providers/+page.server.ts | 31 +-------- src/routes/admin/saml-sps/+page.server.ts | 19 +----- src/routes/admin/skins/+page.server.ts | 4 +- 4 files changed, 72 insertions(+), 50 deletions(-) diff --git a/src/lib/server/validation.ts b/src/lib/server/validation.ts index dde081d..49823ea 100644 --- a/src/lib/server/validation.ts +++ b/src/lib/server/validation.ts @@ -4,6 +4,9 @@ * 공통 판정을 한곳으로 모은다. */ +/** URL/host 검증 결과 공통 형태. */ +export type ValidationResult = { ok: true } | { ok: false; reason: string }; + /** * loopback 호스트 판정. http URL 허용(개발/내부) 여부나 SSRF 게이트에서 공통 사용. * 대괄호 IPv6 표기([::1])도 포함한다. @@ -11,3 +14,68 @@ export function isLoopbackHost(hostname: string): boolean { return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "[::1]" || hostname === "::1"; } + +/** 명백한 SSRF 표적인 클라우드 메타데이터 호스트 집합. */ +const BLOCKED_METADATA_HOSTS = new Set(["metadata.google.internal", "metadata.azure.com", "metadata.azure.internal", "instance-data", "metadata"]); + +/** 클라우드 메타데이터 호스트(GCP/Azure 등) 판정. SSRF 게이트에서 사용. */ +export function isCloudMetadataHost(hostname: string): boolean { + return BLOCKED_METADATA_HOSTS.has(hostname.toLowerCase()); +} + +/** link-local(169.254.0.0/16, AWS IMDS 169.254.169.254 포함) 판정. */ +export function isLinkLocalHost(hostname: string): boolean { + return /^169\.254\./.test(hostname.toLowerCase()); +} + +/** + * SAML ACS/SLO 등 SP URL 검증. 빈 값은 통과(선택 필드). + * https 만 허용하되, http 는 loopback 호스트에 한해 허용(개발/내부). + * @param label 에러 메시지 접두사(예: "ACS URL"). + */ +export function validateSamlUrl(value: string, label: string): ValidationResult { + if (!value) return { ok: true }; + let parsed: URL; + try { + parsed = new URL(value); + } catch { + return { ok: false, reason: `${label}: URL 형식이 올바르지 않습니다.` }; + } + const scheme = parsed.protocol.replace(/:$/, "").toLowerCase(); + if (scheme === "https") return { ok: true }; + if (scheme === "http") { + if (isLoopbackHost(parsed.hostname)) return { ok: true }; + return { ok: false, reason: `${label}: http URL 은 localhost/127.0.0.1 만 허용됩니다.` }; + } + return { ok: false, reason: `${label}: https URL 만 허용됩니다.` }; +} + +/** + * LDAP 호스트가 메타데이터 / link-local 등 명백한 SSRF 표적인지 검사. + * RFC1918 사설망은 사내 LDAP 정상 사용처가 많아 차단하지 않는다. + */ +export function validateLdapHost(host: string): ValidationResult { + const lower = host.toLowerCase(); + if (isCloudMetadataHost(lower)) { + return { ok: false, reason: "클라우드 메타데이터 호스트는 사용할 수 없습니다." }; + } + // 169.254.0.0/16 link-local (AWS IMDS 169.254.169.254 포함) + if (isLinkLocalHost(lower)) { + return { ok: false, reason: "link-local(169.254/16) 주소는 사용할 수 없습니다." }; + } + return { ok: true }; +} + +/** 허용 LDAP 포트: 389(ldap), 636(ldaps), 3268/3269(GC). */ +const ALLOWED_LDAP_PORTS = new Set([389, 636, 3268, 3269]); + +/** LDAP 포트 검증(정수 범위 + 허용 목록). */ +export function validateLdapPort(port: number): ValidationResult { + if (!Number.isInteger(port) || port < 1 || port > 65535) { + return { ok: false, reason: "포트 번호가 올바르지 않습니다." }; + } + if (!ALLOWED_LDAP_PORTS.has(port)) { + return { ok: false, reason: `허용되지 않는 LDAP 포트입니다 (허용: ${[...ALLOWED_LDAP_PORTS].join(", ")}).` }; + } + return { ok: true }; +} diff --git a/src/routes/admin/ldap-providers/+page.server.ts b/src/routes/admin/ldap-providers/+page.server.ts index 7147da2..7810703 100644 --- a/src/routes/admin/ldap-providers/+page.server.ts +++ b/src/routes/admin/ldap-providers/+page.server.ts @@ -7,6 +7,7 @@ import { getRuntimeConfig } from "$lib/server/auth/runtime"; import { encryptSecret } from "$lib/server/crypto/keys"; import { identityProviders } from "$lib/server/db/schema"; import type { LdapProviderConfig } from "$lib/server/ldap/types"; +import { validateLdapHost, validateLdapPort } from "$lib/server/validation"; function buildConfig(fd: FormData): LdapProviderConfig { const port = parseInt(String(fd.get("port") ?? "389"), 10); @@ -49,36 +50,6 @@ function buildConfig(fd: FormData): LdapProviderConfig { return config; } -const ALLOWED_LDAP_PORTS = new Set([389, 636, 3268, 3269]); - -const BLOCKED_METADATA_HOSTS = new Set(["metadata.google.internal", "metadata.azure.com", "metadata.azure.internal", "instance-data", "metadata"]); - -/** - * LDAP 호스트가 메타데이터 / link-local 등 명백한 SSRF 표적인지 검사. - * RFC1918 사설망은 사내 LDAP 정상 사용처가 많아 차단하지 않는다. - */ -function validateLdapHost(host: string): { ok: true } | { ok: false; reason: string } { - const lower = host.toLowerCase(); - if (BLOCKED_METADATA_HOSTS.has(lower)) { - return { ok: false, reason: "클라우드 메타데이터 호스트는 사용할 수 없습니다." }; - } - // 169.254.0.0/16 link-local (AWS IMDS 169.254.169.254 포함) - if (/^169\.254\./.test(lower)) { - return { ok: false, reason: "link-local(169.254/16) 주소는 사용할 수 없습니다." }; - } - return { ok: true }; -} - -function validateLdapPort(port: number): { ok: true } | { ok: false; reason: string } { - if (!Number.isInteger(port) || port < 1 || port > 65535) { - return { ok: false, reason: "포트 번호가 올바르지 않습니다." }; - } - if (!ALLOWED_LDAP_PORTS.has(port)) { - return { ok: false, reason: `허용되지 않는 LDAP 포트입니다 (허용: ${[...ALLOWED_LDAP_PORTS].join(", ")}).` }; - } - return { ok: true }; -} - // ctrls H-ADMIN-4: signingKeySecret 가 미설정인 상태에서 bindPassword 가 입력되면 // 평문 그대로 저장하던 silent fallback 을 제거. 운영 환경 (signingKeySecret 항상 // 존재) 에서 정상 동작, dev 환경에서 secret 미설정 시 admin 에게 명시적 에러로 diff --git a/src/routes/admin/saml-sps/+page.server.ts b/src/routes/admin/saml-sps/+page.server.ts index 5ebc8af..6b5cbc3 100644 --- a/src/routes/admin/saml-sps/+page.server.ts +++ b/src/routes/admin/saml-sps/+page.server.ts @@ -4,7 +4,7 @@ import type { Actions, PageServerLoad } from "./$types"; import { requireAdminContext } from "$lib/server/auth/guards"; import { recordAuditEvent, getRequestMetadata } from "$lib/server/audit/index"; import { samlSps } from "$lib/server/db/schema"; -import { isLoopbackHost } from "$lib/server/validation"; +import { validateSamlUrl } from "$lib/server/validation"; export const load: PageServerLoad = async ({ locals }) => { const { db, tenant } = requireAdminContext(locals); @@ -42,23 +42,6 @@ const ALLOWED_NAMEID_FORMATS = [ "urn:oasis:names:tc:SAML:2.0:nameid-format:transient", ] as const; -function validateSamlUrl(value: string, label: string): { ok: true } | { ok: false; reason: string } { - if (!value) return { ok: true }; - let parsed: URL; - try { - parsed = new URL(value); - } catch { - return { ok: false, reason: `${label}: URL 형식이 올바르지 않습니다.` }; - } - const scheme = parsed.protocol.replace(/:$/, "").toLowerCase(); - if (scheme === "https") return { ok: true }; - if (scheme === "http") { - if (isLoopbackHost(parsed.hostname)) return { ok: true }; - return { ok: false, reason: `${label}: http URL 은 localhost/127.0.0.1 만 허용됩니다.` }; - } - return { ok: false, reason: `${label}: https URL 만 허용됩니다.` }; -} - function parseAllowedAttributes(raw: string): string | null { const trimmed = raw.trim(); if (!trimmed) return null; diff --git a/src/routes/admin/skins/+page.server.ts b/src/routes/admin/skins/+page.server.ts index 3dbe1ee..a8b7b7e 100644 --- a/src/routes/admin/skins/+page.server.ts +++ b/src/routes/admin/skins/+page.server.ts @@ -4,7 +4,7 @@ import type { Actions, PageServerLoad } from "./$types"; import { requireAdminContext } from "$lib/server/auth/guards"; import { clientSkins, oidcClients, samlSps } from "$lib/server/db/schema"; import { invalidateSkinCache } from "$lib/server/skin/resolver"; -import { isLoopbackHost } from "$lib/server/validation"; +import { isLinkLocalHost, isLoopbackHost } from "$lib/server/validation"; const MAX_SKIN_CACHE_TTL_SECONDS = 86400; // 1일 @@ -22,7 +22,7 @@ function validateSkinFetchUrl(raw: string): { ok: true; url: URL } | { ok: false if (isLoopbackHost(host)) { return { ok: false, reason: "loopback 주소는 사용할 수 없습니다." }; } - if (/^127\./.test(host) || /^169\.254\./.test(host)) { + if (/^127\./.test(host) || isLinkLocalHost(host)) { return { ok: false, reason: "내부망/메타데이터 주소는 사용할 수 없습니다." }; } return { ok: true, url }; From 1c8c5c039c9370c4da0515f601f53e314adbcf15 Mon Sep 17 00:00:00 2001 From: Henry Jang Date: Sun, 5 Jul 2026 19:50:46 +0900 Subject: [PATCH 7/8] =?UTF-8?q?feat(i18n):=20auth=20=EC=84=9C=EB=B2=84=20?= =?UTF-8?q?=EC=97=90=EB=9F=AC=20=EB=A9=94=EC=8B=9C=EC=A7=80=20i18n=20(loca?= =?UTF-8?q?ls.locale=20=EA=B8=B0=EB=B0=98=20translate)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - i18n/core.ts: 클라이언트 t() 와 서버 translate() 가 공유하는 lookup/폴백(현재로케일→ko→key) 로직 추출. - i18n/server.ts: translate(locale, key, params) — event.locals.locale 로 SSR/action 에서 번역. - (auth)/{login,signup,find-id,find-password,mfa,reset-password}/+page.server.ts 의 사용자 노출 fail/error 메시지를 키 기반 translate 로 전환(errors 공통 섹션 + 각 섹션 키). ko.json 에 키 추가. 운영자향 로그(IDP_ISSUER_URL 미설정 등)는 제외. vitest 54 passed, svelte-check 0 errors, eslint·prettier 통과. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/lib/i18n.svelte.ts | 50 ++--------------- src/lib/i18n/core.ts | 55 +++++++++++++++++++ src/lib/i18n/ko.json | 6 +- src/lib/i18n/server.ts | 7 +++ src/routes/(auth)/find-id/+page.server.ts | 7 ++- .../(auth)/find-password/+page.server.ts | 7 ++- src/routes/(auth)/login/+page.server.ts | 12 ++-- src/routes/(auth)/mfa/+page.server.ts | 14 +++-- .../(auth)/reset-password/+page.server.ts | 12 ++-- src/routes/(auth)/signup/+page.server.ts | 18 +++--- 10 files changed, 114 insertions(+), 74 deletions(-) create mode 100644 src/lib/i18n/core.ts create mode 100644 src/lib/i18n/server.ts diff --git a/src/lib/i18n.svelte.ts b/src/lib/i18n.svelte.ts index 6e60f67..aea70a3 100644 --- a/src/lib/i18n.svelte.ts +++ b/src/lib/i18n.svelte.ts @@ -1,21 +1,6 @@ -import ko from "./i18n/ko.json"; -import en from "./i18n/en.json"; +import { resolveMessage, type Locale } from "./i18n/core"; -export type Locale = "ko" | "en"; - -interface MessageDictionary { - [key: string]: string | MessageDictionary; -} - -type MessageValue = string | MessageDictionary; - -const messages: Record = { - ko, - en, -}; - -// 현재 로케일에서 키를 찾지 못하면 폴백할 기준 로케일. -const FALLBACK_LOCALE: Locale = "ko"; +export type { Locale }; let currentLocale = $state("ko"); @@ -27,34 +12,7 @@ export function getLocale(): Locale { return currentLocale; } -// 점(.)으로 구분된 키 경로를 사전에서 조회한다. 문자열이 아니거나 경로가 없으면 undefined. -function lookup(dict: MessageValue, keys: string[]): string | undefined { - let message: MessageValue = dict; - - for (const currentKey of keys) { - if (typeof message === "object" && message !== null && currentKey in message) { - message = message[currentKey]; - } else { - return undefined; - } - } - - return typeof message === "string" ? message : undefined; -} - export function t(key: string, params?: Record): string { - const locale = getLocale(); - const keys = key.split("."); - - // 현재 로케일 → ko 폴백 → 원본 key 순으로 해석. - const message = lookup(messages[locale], keys) ?? (locale === FALLBACK_LOCALE ? undefined : lookup(messages[FALLBACK_LOCALE], keys)) ?? key; - - if (!params) { - return message; - } - - return message.replace(/{{(.*?)}}/g, (_, param: string) => { - const trimmedParam = param.trim(); - return params[trimmedParam]?.toString() ?? `{{${trimmedParam}}}`; - }); + // 반응형 $state 로케일을 읽어 core 의 공통 lookup/폴백 로직으로 위임한다. + return resolveMessage(getLocale(), key, params); } diff --git a/src/lib/i18n/core.ts b/src/lib/i18n/core.ts new file mode 100644 index 0000000..41f1219 --- /dev/null +++ b/src/lib/i18n/core.ts @@ -0,0 +1,55 @@ +import ko from "./ko.json"; +import en from "./en.json"; + +export type Locale = "ko" | "en"; + +interface MessageDictionary { + [key: string]: string | MessageDictionary; +} + +type MessageValue = string | MessageDictionary; + +export const messages: Record = { + ko, + en, +}; + +// 현재 로케일에서 키를 찾지 못하면 폴백할 기준 로케일. +export const FALLBACK_LOCALE: Locale = "ko"; + +// 점(.)으로 구분된 키 경로를 사전에서 조회한다. 문자열이 아니거나 경로가 없으면 undefined. +function lookup(dict: MessageValue, keys: string[]): string | undefined { + let message: MessageValue = dict; + + for (const currentKey of keys) { + if (typeof message === "object" && message !== null && currentKey in message) { + message = message[currentKey]; + } else { + return undefined; + } + } + + return typeof message === "string" ? message : undefined; +} + +// {{param}} 형태의 플레이스홀더를 치환한다. +function interpolate(message: string, params?: Record): string { + if (!params) { + return message; + } + + return message.replace(/{{(.*?)}}/g, (_, param: string) => { + const trimmedParam = param.trim(); + return params[trimmedParam]?.toString() ?? `{{${trimmedParam}}}`; + }); +} + +// 로케일 + 키 경로를 실제 메시지로 해석한다. 클라이언트 t() 와 서버 translate() 가 공유하는 단일 lookup 진입점. +// 현재 로케일 → ko 폴백 → 원본 key 순으로 해석한다. +export function resolveMessage(locale: Locale, key: string, params?: Record): string { + const keys = key.split("."); + + const message = lookup(messages[locale], keys) ?? (locale === FALLBACK_LOCALE ? undefined : lookup(messages[FALLBACK_LOCALE], keys)) ?? key; + + return interpolate(message, params); +} diff --git a/src/lib/i18n/ko.json b/src/lib/i18n/ko.json index eff4b7b..2d78496 100644 --- a/src/lib/i18n/ko.json +++ b/src/lib/i18n/ko.json @@ -50,7 +50,11 @@ "passkey_cancelled": "패스키 인증이 취소되었습니다.", "passkey_failed": "패스키 인증에 실패했습니다.", "registered_success": "회원가입이 완료되었습니다. 로그인해 주세요.", - "password_reset_success": "비밀번호가 변경되었습니다. 새 비밀번호로 로그인해 주세요." + "password_reset_success": "비밀번호가 변경되었습니다. 새 비밀번호로 로그인해 주세요.", + "err_missing_credentials": "아이디와 비밀번호를 입력해 주세요.", + "err_rate_limit": "로그인 시도가 너무 많습니다. {{minutes}}분 후 다시 시도해 주세요.", + "err_invalid_credentials": "아이디 또는 비밀번호가 올바르지 않습니다.", + "err_mfa_config": "2단계 인증 설정에 문제가 있습니다. 관리자에게 문의해 주세요." }, "admin": { "title": "관리자 대시보드", diff --git a/src/lib/i18n/server.ts b/src/lib/i18n/server.ts new file mode 100644 index 0000000..5aa9873 --- /dev/null +++ b/src/lib/i18n/server.ts @@ -0,0 +1,7 @@ +import { resolveMessage, type Locale } from "./core"; + +// 서버(SSR/actions)용 번역 헬퍼. 클라이언트 t() 는 $state 기반이라 서버에서 쓸 수 없으므로 +// event.locals.locale 을 명시적으로 받아 동일한 lookup/폴백 로직(core.resolveMessage)을 재사용한다. +export function translate(locale: Locale, key: string, params?: Record): string { + return resolveMessage(locale, key, params); +} diff --git a/src/routes/(auth)/find-id/+page.server.ts b/src/routes/(auth)/find-id/+page.server.ts index fb902b9..20c41cc 100644 --- a/src/routes/(auth)/find-id/+page.server.ts +++ b/src/routes/(auth)/find-id/+page.server.ts @@ -7,6 +7,7 @@ import { users } from "$lib/server/db/schema"; import { sendFindIdEmail } from "$lib/server/email"; import { checkRateLimit } from "$lib/server/ratelimit"; import { getRequestMetadata } from "$lib/server/audit"; +import { translate } from "$lib/i18n/server"; export const load: PageServerLoad = async ({ locals, url, platform }) => { const skinHint = url.searchParams.get("skinHint"); @@ -66,8 +67,10 @@ export const actions: Actions = { .trim() .toLowerCase(); + const locale = event.locals.locale; + if (!email) { - const msg = "이메일을 입력해 주세요."; + const msg = translate(locale, "find_id.err_missing_email"); return fail(400, { error: msg, skinHtml: await resolveSkinForAction(event, false, null, msg) }); } @@ -75,7 +78,7 @@ export const actions: Actions = { const meta = getRequestMetadata(event); const rl = await checkRateLimit(db, `find-id:${meta.ipKey}`, { windowMs: 60 * 60 * 1000, limit: 5 }); if (!rl.allowed) { - const msg = `요청이 너무 많습니다. ${Math.ceil(rl.retryAfterMs / 60000)}분 후 다시 시도해 주세요.`; + const msg = translate(locale, "errors.rate_limit", { minutes: Math.ceil(rl.retryAfterMs / 60000) }); return fail(429, { error: msg, skinHtml: await resolveSkinForAction(event, false, null, msg) }); } diff --git a/src/routes/(auth)/find-password/+page.server.ts b/src/routes/(auth)/find-password/+page.server.ts index 4a629c7..5d9a519 100644 --- a/src/routes/(auth)/find-password/+page.server.ts +++ b/src/routes/(auth)/find-password/+page.server.ts @@ -8,6 +8,7 @@ import { sendPasswordResetEmail, generateToken } from "$lib/server/email"; import { checkRateLimit } from "$lib/server/ratelimit"; import { getRequestMetadata } from "$lib/server/audit"; import { env } from "$env/dynamic/private"; +import { translate } from "$lib/i18n/server"; const RESET_EXPIRY_MS = 60 * 60 * 1000; @@ -72,8 +73,10 @@ export const actions: Actions = { .trim() .toLowerCase(); + const locale = event.locals.locale; + if (!email || !username) { - const msg = "이메일과 아이디를 모두 입력해 주세요."; + const msg = translate(locale, "find_password.err_missing_fields"); return fail(400, { error: msg, skinHtml: await resolveSkinForAction(event, false, undefined, msg) }); } @@ -81,7 +84,7 @@ export const actions: Actions = { const meta = getRequestMetadata(event); const rl = await checkRateLimit(db, `find-password:${meta.ipKey}`, { windowMs: 60 * 60 * 1000, limit: 5 }); if (!rl.allowed) { - const msg = `요청이 너무 많습니다. ${Math.ceil(rl.retryAfterMs / 60000)}분 후 다시 시도해 주세요.`; + const msg = translate(locale, "errors.rate_limit", { minutes: Math.ceil(rl.retryAfterMs / 60000) }); return fail(429, { error: msg, skinHtml: await resolveSkinForAction(event, false, undefined, msg) }); } diff --git a/src/routes/(auth)/login/+page.server.ts b/src/routes/(auth)/login/+page.server.ts index 340445c..4244552 100644 --- a/src/routes/(auth)/login/+page.server.ts +++ b/src/routes/(auth)/login/+page.server.ts @@ -16,6 +16,7 @@ import type { LdapProviderConfig } from "$lib/server/ldap/types"; import { decryptSecret, encryptSecret } from "$lib/server/crypto/keys"; import { resolveSkinHtml, replacePlaceholders, escapeHtml } from "$lib/server/skin/resolver"; import { sanitizeRedirectTarget } from "$lib/server/auth/redirect"; +import { translate } from "$lib/i18n/server"; async function resolveSkinForAction(event: Parameters[0], flashMsg: string, redirectTo: string | null): Promise { const skinHint = event.url.searchParams.get("skinHint"); @@ -90,9 +91,10 @@ export const actions: Actions = { const username = normalizeUsername(String(formData.get("username") ?? "")); const password = String(formData.get("password") ?? ""); const redirectTo = sanitizeRedirectTarget(String(formData.get("redirectTo") ?? "")); + const locale = event.locals.locale; if (!username || !password) { - const msg = "아이디와 비밀번호를 입력해 주세요."; + const msg = translate(locale, "login.err_missing_credentials"); return fail(400, { username, redirectTo, @@ -102,7 +104,7 @@ export const actions: Actions = { } if (!event.locals.db || !event.locals.tenant) { - const msg = event.locals.runtimeError ?? 'D1 binding "DB" 가 준비되지 않았습니다. Wrangler preview/dev 환경에서 실행해 주세요.'; + const msg = event.locals.runtimeError ?? translate(locale, "errors.db_not_ready"); return fail(503, { username, redirectTo, @@ -118,7 +120,7 @@ export const actions: Actions = { const rlKey = `login:${requestMetadata.ipKey}`; const rl = await checkRateLimit(db, rlKey, { windowMs: 15 * 60 * 1000, limit: 10 }); if (!rl.allowed) { - const msg = `로그인 시도가 너무 많습니다. ${Math.ceil(rl.retryAfterMs / 60000)}분 후 다시 시도해 주세요.`; + const msg = translate(locale, "login.err_rate_limit", { minutes: Math.ceil(rl.retryAfterMs / 60000) }); return fail(429, { username, redirectTo, @@ -192,7 +194,7 @@ export const actions: Actions = { detail: { username }, }); - const msg = "아이디 또는 비밀번호가 올바르지 않습니다."; + const msg = translate(locale, "login.err_invalid_credentials"); return fail(400, { username, redirectTo, @@ -205,7 +207,7 @@ export const actions: Actions = { // MFA 단계로 진행 const config = getRuntimeConfig(event.platform); if (!config.signingKeySecret) { - const msg = "MFA 설정 오류: IDP_SIGNING_KEY_SECRET 이 설정되지 않았습니다."; + const msg = translate(locale, "login.err_mfa_config"); return fail(503, { username, redirectTo, diff --git a/src/routes/(auth)/mfa/+page.server.ts b/src/routes/(auth)/mfa/+page.server.ts index ad63015..7cc92be 100644 --- a/src/routes/(auth)/mfa/+page.server.ts +++ b/src/routes/(auth)/mfa/+page.server.ts @@ -11,6 +11,7 @@ import { AMR_PASSWORD, AMR_TOTP, AMR_BACKUP_CODE, amrToAcr, TOTP_CREDENTIAL_TYPE import { getRuntimeConfig } from "$lib/server/auth/runtime"; import { credentials, users } from "$lib/server/db/schema"; import { resolveSkinHtml, replacePlaceholders, escapeHtml } from "$lib/server/skin/resolver"; +import { translate } from "$lib/i18n/server"; export const load: PageServerLoad = async ({ locals, cookies, platform, url }) => { const mfaToken = cookies.get(MFA_PENDING_COOKIE); @@ -85,9 +86,12 @@ export const actions: Actions = { throw redirect(303, "/login"); } + const locale = event.locals.locale; + const config = getRuntimeConfig(event.platform); if (!config.signingKeySecret) { - return fail(503, { error: "MFA 설정 오류가 발생했습니다.", skinHtml: await resolveMfaSkinForAction(event, "MFA 설정 오류가 발생했습니다.") }); + const msg = translate(locale, "mfa_login.err_config"); + return fail(503, { error: msg, skinHtml: await resolveMfaSkinForAction(event, msg) }); } const claims = await verifyMfaPendingToken(mfaToken, config.signingKeySecret); @@ -105,7 +109,7 @@ export const actions: Actions = { } if (!event.locals.db) { - const msg = "DB가 준비되지 않았습니다."; + const msg = translate(locale, "errors.db_not_ready"); return fail(503, { error: msg, skinHtml: await resolveMfaSkinForAction(event, msg) }); } @@ -114,7 +118,7 @@ export const actions: Actions = { limit: 10, }); if (!rl.allowed) { - const msg = "MFA 시도가 너무 많습니다. 잠시 후 다시 시도해 주세요."; + const msg = translate(locale, "mfa_login.err_rate_limit"); return fail(429, { error: msg, skinHtml: await resolveMfaSkinForAction(event, msg) }); } @@ -125,7 +129,7 @@ export const actions: Actions = { const useBackup = formData.get("use_backup") === "1"; if (!code) { - const msg = "인증 코드를 입력해 주세요."; + const msg = translate(locale, "mfa_login.err_missing_code"); return fail(400, { error: msg, skinHtml: await resolveMfaSkinForAction(event, msg) }); } @@ -201,7 +205,7 @@ export const actions: Actions = { detail: { method: useBackup ? "backup_code" : "totp" }, }); - const msg = useBackup ? "백업 코드가 올바르지 않거나 이미 사용되었습니다." : "인증 코드가 올바르지 않습니다. 시간이 맞는지 확인해 주세요."; + const msg = useBackup ? translate(locale, "mfa_login.err_invalid_backup") : translate(locale, "mfa_login.err_invalid_totp"); return fail(400, { error: msg, skinHtml: await resolveMfaSkinForAction(event, msg) }); } diff --git a/src/routes/(auth)/reset-password/+page.server.ts b/src/routes/(auth)/reset-password/+page.server.ts index e23ddcb..2e128fd 100644 --- a/src/routes/(auth)/reset-password/+page.server.ts +++ b/src/routes/(auth)/reset-password/+page.server.ts @@ -12,6 +12,7 @@ import { sanitizeRedirectTarget } from "$lib/server/auth/redirect"; import { resolveSkinHtml, replacePlaceholders, escapeHtml } from "$lib/server/skin/resolver"; import { checkRateLimit } from "$lib/server/ratelimit"; import { getRequestMetadata } from "$lib/server/audit"; +import { translate } from "$lib/i18n/server"; async function resolveSkin(skinHint: string | null, locals: App.Locals, platform: App.Platform | undefined, token: string | null, redirectTo: string | null, flashMsg = ""): Promise { if (!skinHint || !locals.db || !locals.tenant) return null; @@ -74,6 +75,7 @@ export const actions: Actions = { const redirectTo = sanitizeRedirectTarget(String(formData.get("redirectTo") ?? "")); const skinHint = String(formData.get("skinHint") ?? ""); + const locale = event.locals.locale; const failWithSkin = async (msg: string) => fail(400, { error: msg, skinHtml: await resolveSkin(skinHint || null, event.locals, event.platform, token || null, redirectTo, msg) }); // ctrls C8: 토큰 제출 브루트포스/자동화 방어. 토큰이 256bit CSPRNG 라 추측 실익은 @@ -81,12 +83,12 @@ export const actions: Actions = { const meta = getRequestMetadata(event); const rl = await checkRateLimit(db, `reset-password:${meta.ipKey}`, { windowMs: 15 * 60 * 1000, limit: 10 }); if (!rl.allowed) { - return failWithSkin(`요청이 너무 많습니다. ${Math.ceil(rl.retryAfterMs / 60000)}분 후 다시 시도해 주세요.`); + return failWithSkin(translate(locale, "errors.rate_limit", { minutes: Math.ceil(rl.retryAfterMs / 60000) })); } - if (!token) return failWithSkin("유효하지 않은 요청입니다."); - if (password.length < 8) return failWithSkin("비밀번호는 8자 이상이어야 합니다."); - if (password !== confirmPassword) return failWithSkin("비밀번호가 일치하지 않습니다."); + if (!token) return failWithSkin(translate(locale, "reset_password.err_invalid_request")); + if (password.length < 8) return failWithSkin(translate(locale, "reset_password.err_password_short")); + if (password !== confirmPassword) return failWithSkin(translate(locale, "reset_password.err_password_mismatch")); const tokenHash = await hashToken(token); const now = new Date(); @@ -97,7 +99,7 @@ export const actions: Actions = { .where(and(eq(passwordResetTokens.tokenHash, tokenHash), isNull(passwordResetTokens.usedAt))) .limit(1); - if (!record || record.expiresAt < now) return failWithSkin("링크가 만료되었거나 이미 사용된 링크입니다."); + if (!record || record.expiresAt < now) return failWithSkin(translate(locale, "reset_password.err_expired_link")); const hashedPw = await hashPassword(password); diff --git a/src/routes/(auth)/signup/+page.server.ts b/src/routes/(auth)/signup/+page.server.ts index 3c48dd0..ce1c8d2 100644 --- a/src/routes/(auth)/signup/+page.server.ts +++ b/src/routes/(auth)/signup/+page.server.ts @@ -9,6 +9,7 @@ import { resolve } from "$app/paths"; import { sanitizeRedirectTarget } from "$lib/server/auth/redirect"; import { checkRateLimit } from "$lib/server/ratelimit"; import { getRequestMetadata } from "$lib/server/audit"; +import { translate } from "$lib/i18n/server"; export const load: PageServerLoad = async ({ locals, url, platform }) => { const skinHint = url.searchParams.get("skinHint"); @@ -70,34 +71,35 @@ export const actions: Actions = { const password = String(formData.get("password") ?? ""); const confirmPassword = String(formData.get("confirmPassword") ?? ""); + const locale = event.locals.locale; const failSkin = async (status: number, msg: string) => fail(status, { error: msg, skinHtml: await resolveSkinForAction(event, msg) }); // IP 기반 레이트리밋 — 60분/5회. const meta = getRequestMetadata(event); const rl = await checkRateLimit(db, `signup:${meta.ipKey}`, { windowMs: 60 * 60 * 1000, limit: 5 }); if (!rl.allowed) { - return failSkin(429, `가입 시도가 너무 많습니다. ${Math.ceil(rl.retryAfterMs / 60000)}분 후 다시 시도해 주세요.`); + return failSkin(429, translate(locale, "signup.err_rate_limit", { minutes: Math.ceil(rl.retryAfterMs / 60000) })); } - if (!username || !email || !password) return failSkin(400, "모든 필드를 입력해 주세요."); - if (!/^[a-z0-9_]{3,32}$/.test(username)) return failSkin(400, "아이디는 영문 소문자, 숫자, _만 사용 가능하며 3~32자여야 합니다."); - if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) return failSkin(400, "올바른 이메일 주소를 입력해 주세요."); - if (password.length < 8) return failSkin(400, "비밀번호는 8자 이상이어야 합니다."); - if (password !== confirmPassword) return failSkin(400, "비밀번호가 일치하지 않습니다."); + if (!username || !email || !password) return failSkin(400, translate(locale, "signup.err_missing_fields")); + if (!/^[a-z0-9_]{3,32}$/.test(username)) return failSkin(400, translate(locale, "signup.err_invalid_username")); + if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) return failSkin(400, translate(locale, "signup.err_invalid_email")); + if (password.length < 8) return failSkin(400, translate(locale, "signup.err_password_short")); + if (password !== confirmPassword) return failSkin(400, translate(locale, "signup.err_password_mismatch")); const [existingByUsername] = await db .select({ id: users.id }) .from(users) .where(and(eq(users.tenantId, tenant.id), eq(users.username, username))) .limit(1); - if (existingByUsername) return failSkin(409, "이미 사용 중인 아이디입니다."); + if (existingByUsername) return failSkin(409, translate(locale, "signup.err_username_taken")); const [existingByEmail] = await db .select({ id: users.id }) .from(users) .where(and(eq(users.tenantId, tenant.id), eq(users.email, email))) .limit(1); - if (existingByEmail) return failSkin(409, "이미 사용 중인 이메일입니다."); + if (existingByEmail) return failSkin(409, translate(locale, "signup.err_email_taken")); const hashedPw = await hashPassword(password); const userId = crypto.randomUUID(); From cc44d7ca036531e4d3eb401a85f1d1f72197dfe2 Mon Sep 17 00:00:00 2001 From: Henry Jang Date: Sun, 5 Jul 2026 20:03:57 +0900 Subject: [PATCH 8/8] =?UTF-8?q?feat(i18n):=20admin=20=EC=BD=98=EC=86=94=20?= =?UTF-8?q?=EC=98=81=EC=96=B4=ED=99=94=20=E2=80=94=20en.json=20ko=20?= =?UTF-8?q?=EB=8C=80=EC=B9=AD(476=ED=82=A4)=20+=20admin=20svelte=20t()=20?= =?UTF-8?q?=EC=A0=84=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - en.json 을 ko.json 과 1:1 대칭으로 완성(30개 최상위 섹션, 476키 커버). admin/users/oidc/saml/ ldap/skins/signing_keys/조직/audit/profile/passkeys/mfa_manage 등 admin 섹션 영어 번역 추가. → locale=en 사용자가 admin 콘솔을 영어로 사용 가능(기존엔 ko 폴백). - admin svelte 잔존 하드코딩 한국어를 t() 로 전환: dashboard, admin/login, 조직 CRUD placeholder, oidc-clients/[id], saml-sps/[id], users/[id] 서비스권한 섹션, skins guide 등 11+파일. 신규 키는 ko.json 에도 한국어 값 추가(ko 회귀 없음). - ※ en.json 번역은 자동 작성 — 배포 전 원어민 검수 권장. vitest 54 passed, svelte-check 0 errors, eslint·prettier 통과, build 성공, JSON 유효. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/lib/i18n/en.json | 433 +++++++++++++++++- src/lib/i18n/ko.json | 55 ++- src/routes/admin/+page.svelte | 4 +- src/routes/admin/departments/+page.svelte | 19 +- src/routes/admin/ldap-providers/+page.svelte | 2 +- src/routes/admin/login/+page.svelte | 18 +- .../admin/oidc-clients/[id]/+page.svelte | 23 +- src/routes/admin/parts/+page.svelte | 19 +- src/routes/admin/positions/+page.svelte | 11 +- src/routes/admin/saml-sps/+page.svelte | 4 +- src/routes/admin/saml-sps/[id]/+page.svelte | 23 +- src/routes/admin/skins/+page.svelte | 2 +- src/routes/admin/skins/guide/+page.svelte | 12 +- src/routes/admin/teams/+page.svelte | 19 +- src/routes/admin/users/[id]/+page.svelte | 32 +- 15 files changed, 598 insertions(+), 78 deletions(-) diff --git a/src/lib/i18n/en.json b/src/lib/i18n/en.json index 51a833a..d81d5ad 100644 --- a/src/lib/i18n/en.json +++ b/src/lib/i18n/en.json @@ -50,7 +50,46 @@ "passkey_cancelled": "Passkey authentication was cancelled.", "passkey_failed": "Passkey authentication failed.", "registered_success": "Your account has been created. Please log in.", - "password_reset_success": "Your password has been changed. Please log in with your new password." + "password_reset_success": "Your password has been changed. Please log in with your new password.", + "err_missing_credentials": "Please enter your username and password.", + "err_rate_limit": "Too many login attempts. Please try again in {{minutes}} minutes.", + "err_invalid_credentials": "The username or password is incorrect.", + "err_mfa_config": "There is a problem with the two-factor authentication configuration. Please contact your administrator." + }, + "admin": { + "title": "Admin Dashboard", + "dashboard_subtitle": "Quickly review the current operational status of the default tenant.", + "auth_protocols": "Authentication Protocols", + "dashboard": "Dashboard", + "users": "Users", + "oidc_clients": "OIDC Clients", + "saml_sps": "SAML SPs", + "signing_keys": "Signing Keys", + "audit": "Audit Log", + "logout": "Log out", + "org": "Organization", + "positions": "Positions", + "departments": "Departments", + "teams": "Teams", + "parts": "Parts", + "ldap_providers": "LDAP Providers", + "skins": "Login Skins" + }, + "admin_login": { + "subtitle": "Admin sign-in", + "err_options": "Failed to request options", + "err_verify": "Authentication failed", + "user_login_prefix": "General users can log in ", + "user_login_link": "here", + "user_login_suffix": "." + }, + "account": { + "profile": "My Profile", + "mfa": "Two-Factor Authentication", + "passkeys": "Passkeys" + }, + "logout": { + "in_progress": "Logging out..." }, "mfa_login": { "title": "Two-Factor Authentication", @@ -63,6 +102,330 @@ "use_totp": "Sign in with an authenticator code", "back_to_login": "← Back to login" }, + "mfa_manage": { + "title": "Two-Factor Authentication Settings", + "back_to_home": "← Home", + "backup_codes_generated": "Backup codes generated", + "backup_codes_warning": "These codes are shown only now. Store them in a safe place. Each code can be used only once.", + "setup_title": "Register Authenticator App", + "setup_hint": "Scan the QR code with a TOTP app such as Google Authenticator or Authy.", + "qr_loading": "Loading...", + "qr_error": "Failed to generate QR code", + "manual_entry": "Enter manually (show key)", + "code_input_label": "Enter the 6-digit code shown in the app", + "register_complete": "Complete Registration", + "new_qr": "Generate new QR code", + "enrolled_badge": "A TOTP authenticator is registered.", + "enrolled_date": "Registered:", + "backup_codes_label": "Backup Codes", + "backup_codes_remaining": "{{count}} remaining", + "backup_codes_low": "— codes are running low", + "backup_codes_empty": "— regeneration required", + "regenerate": "Regenerate", + "regenerate_confirm": "All existing backup codes will be deleted and reissued. Continue?", + "totp_placeholder": "TOTP code", + "delete_input_placeholder": "Enter the current TOTP code to delete", + "delete_button": "Delete Authenticator", + "delete_confirm": "The TOTP authenticator and all backup codes will be deleted. Continue?", + "not_enrolled_hint": "Enabling two-factor authentication strengthens your account security. An authenticator app such as Google Authenticator or Authy is required.", + "start_setup": "Start Authenticator Setup" + }, + "passkeys": { + "title": "Manage Passkeys", + "subtitle": "Sign in without a password using your fingerprint, Face ID, a security key, and more.", + "name_label": "Passkey name (optional)", + "registered_date": "Registered:", + "last_used": "· Last used:", + "delete_confirm": "Delete this passkey?", + "delete_password_prompt": "Enter your password to confirm your identity. (If your account has no password set, leave it blank.)", + "empty": "No passkeys have been registered.", + "registering": "Registering...", + "register": "Register Passkey" + }, + "profile": { + "title": "My Profile", + "saved": "Your profile has been saved.", + "basic_info": "Basic Information", + "given_name": "Given Name", + "family_name": "Family Name", + "display_name": "Display Name", + "birthdate": "Date of Birth", + "phone": "Phone Number", + "bio": "Bio", + "locale": "Language", + "timezone": "Time Zone", + "locale_settings": "Regional Settings", + "org_membership": "Organization Membership", + "team_label": "Team", + "part_label": "Part" + }, + "audit": { + "title": "Audit Log", + "showing": "Showing {{count}} ({{pageSize}} per page)", + "event_kind": "Event Type", + "outcome": "Outcome", + "success": "Success", + "failure": "Failure", + "col_time": "Time", + "col_user": "User", + "col_event": "Event", + "col_ip": "IP", + "col_detail": "Details", + "empty": "No audit logs.", + "next_page": "Next page →" + }, + "departments": { + "title": "Department Management", + "add_btn": "+ Add Department", + "create_title": "Add New Department", + "name_label": "Department Name *", + "name_placeholder": "e.g. Engineering Division", + "code_placeholder": "e.g. DEV", + "parent_label": "Parent Department", + "parent_none": "None (top level)", + "display_order": "Display Order", + "col_name": "Department Name", + "col_parent": "Parent Department", + "empty": "No departments have been registered.", + "delete_confirm": "Delete this? Child departments will be detached from their parent." + }, + "positions": { + "title": "Position Management", + "add_btn": "+ Add Position", + "create_title": "Add New Position", + "name_label": "Position Name *", + "name_placeholder": "e.g. Manager", + "code_placeholder": "e.g. MGR", + "level_label": "Level (higher is more senior)", + "col_name": "Position Name", + "col_level": "Level", + "empty": "No positions have been registered." + }, + "teams": { + "title": "Team Management", + "add_btn": "+ Add Team", + "create_title": "Add New Team", + "name_label": "Team Name *", + "name_placeholder": "e.g. Backend Team", + "code_placeholder": "e.g. BE", + "dept_label": "Department", + "dept_none": "None (standalone team)", + "col_name": "Team Name", + "col_dept": "Department", + "empty": "No teams have been registered." + }, + "parts": { + "title": "Part Management", + "add_btn": "+ Add Part", + "create_title": "Add New Part", + "name_label": "Part Name *", + "name_placeholder": "e.g. iOS Part", + "code_placeholder": "e.g. IOS", + "team_label": "Team", + "team_none": "None (standalone part)", + "col_name": "Part Name", + "col_team": "Team", + "col_dept": "Department", + "empty": "No parts have been registered." + }, + "signing_keys": { + "title": "Signing Key Management", + "rotate_btn": "Rotate Key", + "rotate_confirm": "This will create a new key and deactivate the current active key.\nContinue?", + "rotated_title": "Key rotation complete", + "new_kid_label": "New KID:", + "col_alg": "Algorithm", + "col_use": "Use", + "col_cert": "Certificate", + "col_created": "Created", + "col_rotated": "Rotated", + "col_expires": "Expires", + "cert_yes": "Yes", + "cert_no": "No", + "empty": "No signing keys have been registered. Click the Rotate Key button to create your first key." + }, + "users": { + "title": "User Management", + "add_btn": "+ Add User", + "search_placeholder": "Search by email, username, or name", + "search_empty": "No results found.", + "create_title": "Add New User", + "email_label": "Email *", + "username_label": "Username (auto-generated if blank)", + "display_name_label": "Name", + "role_label": "Role *", + "password_label": "Password * (at least 8 characters)", + "role_user": "Regular User", + "role_admin": "Administrator", + "role_user_short": "User", + "status_active": "Active", + "status_disabled": "Disabled", + "status_locked": "Locked", + "action_disable": "Disable", + "action_enable": "Enable", + "action_unlock": "Unlock", + "reset_password": "Reset Password", + "new_password_placeholder": "New password (at least 8 characters)", + "col_id_email": "Username / Email", + "col_name": "Name", + "col_role": "Role", + "empty": "No users have been registered.", + "delete_confirm": "Delete this user?" + }, + "user_detail": { + "back": "← List", + "saved": "Saved.", + "profile_section": "Profile Information", + "given_name": "Given Name", + "family_name": "Family Name", + "display_name": "Display Name", + "phone": "Phone Number", + "birthdate": "Date of Birth", + "bio": "Bio", + "locale": "Language", + "timezone": "Time Zone", + "address_street": "Street Address", + "address_locality": "City", + "address_region": "State/Province", + "address_postal_code": "Postal Code", + "address_country": "Country", + "role": "Role", + "status": "Status", + "role_user": "Regular User", + "role_admin": "Administrator", + "status_active": "Active", + "status_disabled": "Disabled", + "status_locked": "Locked", + "dept_section": "Department Membership", + "dept_empty": "No department memberships.", + "dept_select": "Select department", + "position_none": "No position", + "job_title_placeholder": "Job title (e.g. Team Lead)", + "primary": "Primary", + "team_section": "Team Membership", + "team_empty": "No team memberships.", + "team_select": "Select team", + "part_section": "Part Membership", + "part_empty": "No part memberships.", + "part_select": "Select part", + "part_job_title_placeholder": "Job title (e.g. Part Lead)", + "svc_section": "Service Access", + "svc_desc": "Deny by default — SSO is rejected when no mapping exists. Only one mapping is allowed per service.", + "svc_revoked": "Revoked", + "svc_expired": "Expired", + "svc_active": "Active", + "svc_role_none": "(no role)", + "svc_delete_confirm": "Delete this mapping?", + "svc_expires": "Expiry Date", + "svc_renew": "Update", + "svc_empty": "No services are mapped.", + "svc_service": "Service", + "svc_select": "Select...", + "svc_expires_optional": "Expiry Date (optional)", + "svc_add": "Add Mapping" + }, + "ldap": { + "title": "LDAP Providers", + "add_btn": "+ Add Provider", + "create_title": "New LDAP Provider", + "added_success": "The LDAP provider has been added. Test it and then enable it.", + "saved": "Saved.", + "name_label": "Name *", + "name_placeholder": "Corporate LDAP", + "tls_label": "TLS Mode", + "tls_none": "None (ldap://)", + "tls_tls": "TLS (ldaps://)", + "tls_starttls": "STARTTLS", + "host_label": "Host *", + "port_label": "Port", + "base_dn_label": "Base DN", + "auth_section": "Authentication Method — Admin Bind + Search (when there are multiple OUs) or DN Pattern", + "auth_hint": "If you enter an Admin Bind DN, the Search method is used. Leave it blank to use the user DN pattern.", + "bind_dn_label": "Admin Bind DN", + "bind_pw_label": "Admin Bind Password", + "search_filter_label": "User Search Filter", + "dn_pattern_label": "User DN Pattern", + "dn_pattern_hint": "(when Admin Bind is not used)", + "attr_section": "Attribute Mapping (defaults: mail, cn, givenName, sn)", + "attr_email": "Email Attribute", + "attr_display": "Display Name Attribute", + "attr_given": "Given Name Attribute", + "attr_family": "Family Name Attribute", + "enable": "Enable", + "col_name": "Name", + "col_host": "Host", + "col_dn_pattern": "User DN Pattern", + "col_created": "Created", + "empty": "No LDAP providers have been registered.", + "delete_confirm": "Delete this LDAP provider?" + }, + "oidc": { + "title": "OIDC Clients", + "add_btn": "+ Add Client", + "create_title": "New OIDC Client", + "created_title": "Client created", + "created_secret_hint": "The secret is shown only now. Store it in a safe place.", + "secret_regenerated_title": "Secret regenerated", + "secret_regenerated_hint": "The previous secret can no longer be used.", + "name_label": "Name *", + "redirect_uris_label": "Redirect URIs * (one per line)", + "post_logout_label": "Post-Logout Redirect URIs (one per line)", + "auth_method_label": "Authentication Method", + "auth_method_none": "none (public client)", + "pkce_required": "PKCE Required", + "allow_wildcard_redirect_uri": "Allow wildcard redirect_uri matching (security risk)", + "allow_wildcard_redirect_uri_hint": "Allows matching redirect URI patterns that contain *. This can lead to token theft in the event of a subdomain takeover, so enable it only for trusted domains.", + "enabled": "Active", + "col_name_id": "Name / Client ID", + "col_auth": "Auth", + "col_created": "Created", + "empty": "No clients have been registered.", + "regenerate_secret": "Regenerate Secret", + "regenerate_confirm": "Regenerate the secret? The existing secret will be invalidated immediately.", + "delete_confirm": "Delete this client?" + }, + "saml": { + "title": "SAML SP Management", + "add_btn": "+ Add SP", + "create_title": "Register New SAML SP", + "metadata_hint": "Auto-fill from SP metadata XML", + "metadata_optional": "(optional)", + "metadata_placeholder": "Paste ", + "parse_btn": "Parse Metadata →", + "name_label": "Name *", + "entity_id_label": "Entity ID *", + "acs_url_label": "ACS URL *", + "slo_url_label": "SLO URL", + "name_id_label": "NameID Format", + "sign_assertion": "Sign Assertion", + "encrypt_assertion": "Encrypt Assertion (SP certificate required)", + "sign_response": "Sign Response", + "want_signed": "Require Signed AuthnRequest", + "cert_label": "SP Certificate (PEM, optional)", + "allowed_attrs_label": "Allowed Attributes (comma-separated; if blank, only email, username, displayName are sent)", + "col_name_entity": "Name / Entity ID", + "col_acs": "ACS URL", + "col_sign": "Signing", + "col_created": "Created", + "enabled": "Active", + "empty": "No SAML SPs have been registered.", + "delete_confirm": "Delete this SP?", + "parse_error_xml": "XML parse error", + "parse_error_generic": "Parse failed" + }, + "roles": { + "list_title": "Roles", + "count": "{{count}}", + "empty": "No roles have been registered.", + "edit": "Edit", + "key_placeholder": "key (e.g. admin)", + "add": "Add Role", + "delete_confirm": "Delete role '{{key}}'? User mappings assigned this role will have their role set to null." + }, + "poc": { + "title": "Workers Environment PoC", + "subtitle": "Kickoff M0 pre-check. Each endpoint returns its result as JSON on a GET request." + }, "signup": { "title": "Sign Up", "subtitle": "Create an account to get started.", @@ -103,5 +466,73 @@ "confirm_label": "Confirm Password", "submit": "Change Password", "invalid_link": "This link has expired or is invalid." + }, + "skins": { + "title": "Login Skins", + "add_btn": "+ Add Skin", + "create_title": "New Login Skin", + "created_success": "The skin has been added.", + "updated_success": "The skin has been updated. (The cache was also cleared automatically.)", + "cache_invalidated": "The cache has been cleared.", + "client_type_label": "Client Type", + "client_label": "Client", + "fetch_url_label": "Skin URL", + "fetch_secret_label": "Authentication Secret (X-IDP-Token)", + "fetch_secret_placeholder": "Leave blank if none", + "cache_ttl_label": "Cache TTL (seconds)", + "skin_type_label": "Skin Type", + "skin_type_login": "Log in", + "skin_type_signup": "Sign up", + "skin_type_find_id": "Find Username", + "skin_type_find_password": "Reset Password", + "skin_type_mfa": "OTP (MFA)", + "skin_type_reset_password": "Reset Password", + "col_client": "Client", + "col_skin_type": "Skin Type", + "col_fetch_url": "Skin URL", + "col_ttl": "TTL", + "col_created": "Created", + "empty": "No skins have been registered.", + "delete_confirm": "Delete this skin?", + "invalidate_cache": "Clear Cache", + "guide_link": "View Guide", + "placeholder_guide_title": "Placeholder Guide", + "placeholder_form_action": "Form action (POSTs to the current URL if empty)", + "placeholder_redirect_to": "Redirect target URL after login", + "placeholder_skin_hint": "Skin hint value (used in a hidden input)", + "guide_title": "Custom Skin Development Guide", + "guide_subtitle": "Fetch HTML from an external URL to fully customize the login and sign-up screens.", + "guide_overview_title": "Overview", + "guide_overview_desc": "The IDP lets you register custom skin URLs for the following four pages per OIDC client / SAML SP. When a skin URL is registered, the IDP fetches the URL and returns its HTML when the page is accessed. If none is registered or the fetch fails, the built-in default UI is shown.", + "guide_flow_title": "How It Works", + "guide_flow_1": "When a user starts an OIDC/SAML login flow, the IDP redirects to the login page.", + "guide_flow_2": "The IDP looks up the requested client's skin settings in the database.", + "guide_flow_3": "If a skin URL exists, it checks the R2 cache; if the TTL has expired or there is no cache, it fetches the HTML from the skin server.", + "guide_flow_4": "The IDP replaces placeholders in the HTML (such as {{IDP_FORM_ACTION}}) with actual values and returns it to the browser.", + "guide_flow_5": "When the user submits the skin form, the IDP processes it and redirects to the client.", + "guide_placeholders_title": "Placeholders (Template Variables)", + "guide_placeholder_col_name": "Placeholder", + "guide_placeholder_col_desc": "Description", + "guide_auth_title": "X-IDP-Token Authentication Header", + "guide_auth_desc": "If you set an authentication secret on the skin URL, the IDP includes that value in the X-IDP-Token header when fetching. The skin server can verify this header to confirm the request came from the IDP.", + "guide_example_title": "Example Skin HTML (Login)", + "guide_example_login_desc": "Below is a minimal implementation example of a login skin. It works as long as it includes {{IDP_FORM_ACTION}} and the two hidden inputs.", + "guide_example_note_title": "Notes", + "guide_example_note_1": "The form method must be POST.", + "guide_example_note_2": "You must use the exact field names username and password.", + "guide_example_note_3": "Without the redirectTo and skinHint hidden inputs, the post-login redirect may not work.", + "guide_cache_title": "Cache Behavior", + "guide_cache_1": "Skin HTML is cached in Cloudflare R2 and reused for the configured TTL (seconds).", + "guide_cache_2": "When the TTL expires, the next request fetches fresh HTML from the skin server and refreshes the cache.", + "guide_cache_3": "Click the 'Clear Cache' button on the skin list page to invalidate the cache immediately.", + "guide_setup_title": "How to Register", + "guide_setup_1": "Prepare a server (or CDN) to host the skin HTML and obtain a public URL.", + "guide_setup_2": "If authentication is required, decide on a secret value and add logic to your server to verify the X-IDP-Token header.", + "guide_setup_3": "On the Login Skins management page, enter the target client, skin type, URL, secret, and TTL, and add it.", + "guide_setup_4": "After adding, enable it with the toggle to apply the custom skin in that client's login flow.", + "guide_code_comment": "// Example request verification on the skin server (Node.js)", + "guide_example_login": "Log in", + "guide_example_username": "Username", + "guide_example_password": "Password" } } diff --git a/src/lib/i18n/ko.json b/src/lib/i18n/ko.json index 2d78496..de9fc85 100644 --- a/src/lib/i18n/ko.json +++ b/src/lib/i18n/ko.json @@ -58,6 +58,8 @@ }, "admin": { "title": "관리자 대시보드", + "dashboard_subtitle": "기본 tenant 의 현재 운영 현황을 빠르게 확인할 수 있습니다.", + "auth_protocols": "인증 프로토콜", "dashboard": "대시보드", "users": "사용자", "oidc_clients": "OIDC 클라이언트", @@ -73,6 +75,14 @@ "ldap_providers": "LDAP 프로바이더", "skins": "로그인 스킨" }, + "admin_login": { + "subtitle": "관리자 로그인", + "err_options": "옵션 요청 실패", + "err_verify": "인증 실패", + "user_login_prefix": "일반 사용자는 ", + "user_login_link": "여기", + "user_login_suffix": "에서 로그인하세요." + }, "account": { "profile": "내 프로필", "mfa": "2단계 인증", @@ -169,6 +179,8 @@ "add_btn": "+ 부서 추가", "create_title": "새 부서 추가", "name_label": "부서명 *", + "name_placeholder": "예: 개발본부", + "code_placeholder": "예: DEV", "parent_label": "상위 부서", "parent_none": "없음 (최상위)", "display_order": "표시 순서", @@ -182,6 +194,8 @@ "add_btn": "+ 직급 추가", "create_title": "새 직급 추가", "name_label": "직급명 *", + "name_placeholder": "예: 과장", + "code_placeholder": "예: MGR", "level_label": "레벨 (높을수록 고위)", "col_name": "직급명", "col_level": "레벨", @@ -192,6 +206,8 @@ "add_btn": "+ 팀 추가", "create_title": "새 팀 추가", "name_label": "팀명 *", + "name_placeholder": "예: 백엔드팀", + "code_placeholder": "예: BE", "dept_label": "소속 부서", "dept_none": "없음 (독립 팀)", "col_name": "팀명", @@ -203,6 +219,8 @@ "add_btn": "+ 파트 추가", "create_title": "새 파트 추가", "name_label": "파트명 *", + "name_placeholder": "예: iOS파트", + "code_placeholder": "예: IOS", "team_label": "소속 팀", "team_none": "없음 (독립 파트)", "col_name": "파트명", @@ -290,7 +308,21 @@ "part_section": "파트 소속", "part_empty": "소속된 파트가 없습니다.", "part_select": "파트 선택", - "part_job_title_placeholder": "직책 (예: 파트장)" + "part_job_title_placeholder": "직책 (예: 파트장)", + "svc_section": "서비스 권한", + "svc_desc": "기본 deny — 매핑이 없으면 SSO 가 거부됩니다. 서비스 별로 1 매핑만 허용.", + "svc_revoked": "취소됨", + "svc_expired": "만료됨", + "svc_active": "활성", + "svc_role_none": "(role 없음)", + "svc_delete_confirm": "이 매핑을 삭제하시겠습니까?", + "svc_expires": "만료일", + "svc_renew": "갱신", + "svc_empty": "매핑된 서비스가 없습니다.", + "svc_service": "서비스", + "svc_select": "선택...", + "svc_expires_optional": "만료일 (optional)", + "svc_add": "매핑 추가" }, "ldap": { "title": "LDAP 프로바이더", @@ -299,6 +331,7 @@ "added_success": "LDAP 프로바이더가 추가되었습니다. 테스트 후 활성화하세요.", "saved": "저장되었습니다.", "name_label": "이름 *", + "name_placeholder": "사내 LDAP", "tls_label": "TLS 모드", "tls_none": "없음 (ldap://)", "tls_tls": "TLS (ldaps://)", @@ -376,7 +409,18 @@ "col_created": "생성", "enabled": "활성", "empty": "등록된 SAML SP 가 없습니다.", - "delete_confirm": "SP를 삭제하시겠습니까?" + "delete_confirm": "SP를 삭제하시겠습니까?", + "parse_error_xml": "XML 파싱 오류", + "parse_error_generic": "파싱 실패" + }, + "roles": { + "list_title": "Role 목록", + "count": "{{count}}개", + "empty": "등록된 role 이 없습니다.", + "edit": "편집", + "key_placeholder": "key (예: admin)", + "add": "role 추가", + "delete_confirm": "role '{{key}}' 을 삭제하시겠습니까? 이 role 이 부여된 사용자 매핑은 role 이 null 로 설정됩니다." }, "poc": { "title": "Workers 환경 PoC", @@ -428,6 +472,7 @@ "add_btn": "+ 스킨 추가", "create_title": "새 로그인 스킨", "created_success": "스킨이 추가되었습니다.", + "updated_success": "스킨이 수정되었습니다. (캐시도 자동으로 초기화되었습니다.)", "cache_invalidated": "캐시가 삭제되었습니다.", "client_type_label": "클라이언트 타입", "client_label": "클라이언트", @@ -484,6 +529,10 @@ "guide_setup_1": "스킨 HTML을 호스팅할 서버(또는 CDN)를 준비하고 공개 URL을 확보합니다.", "guide_setup_2": "인증이 필요한 경우 시크릿 값을 정하고 X-IDP-Token 헤더를 검증하는 로직을 서버에 추가합니다.", "guide_setup_3": "로그인 스킨 관리 페이지에서 대상 클라이언트, 스킨 타입, URL, 시크릿, TTL을 입력하고 추가합니다.", - "guide_setup_4": "추가 후 토글로 활성화하면 해당 클라이언트의 로그인 흐름에서 커스텀 스킨이 적용됩니다." + "guide_setup_4": "추가 후 토글로 활성화하면 해당 클라이언트의 로그인 흐름에서 커스텀 스킨이 적용됩니다.", + "guide_code_comment": "// 스킨 서버에서 요청 검증 예시 (Node.js)", + "guide_example_login": "로그인", + "guide_example_username": "아이디", + "guide_example_password": "비밀번호" } } diff --git a/src/routes/admin/+page.svelte b/src/routes/admin/+page.svelte index 2ef7d41..577650f 100644 --- a/src/routes/admin/+page.svelte +++ b/src/routes/admin/+page.svelte @@ -8,11 +8,11 @@ const { data } = $props<{ data: PageData }>();

{t("admin.title")}

-

기본 tenant 의 현재 운영 현황을 빠르게 확인할 수 있습니다.

+

{t("admin.dashboard_subtitle")}

-

인증 프로토콜

+

{t("admin.auth_protocols")}

{t("admin.users")}

diff --git a/src/routes/admin/departments/+page.svelte b/src/routes/admin/departments/+page.svelte index 0b46504..3e66bf7 100644 --- a/src/routes/admin/departments/+page.svelte +++ b/src/routes/admin/departments/+page.svelte @@ -49,7 +49,7 @@ const STATUS_COLOR: Record = { type="text" name="name" required - placeholder="예: 개발본부" + placeholder={t("departments.name_placeholder")} class="mt-1 w-full rounded-md border border-gray-300 px-3 py-1.5 text-sm focus:border-blue-500 focus:outline-none" />
@@ -58,7 +58,7 @@ const STATUS_COLOR: Record = { id="dept-code" type="text" name="code" - placeholder="예: DEV" + placeholder={t("departments.code_placeholder")} class="mt-1 w-full rounded-md border border-gray-300 px-3 py-1.5 text-sm focus:border-blue-500 focus:outline-none" />
@@ -125,8 +125,19 @@ const STATUS_COLOR: Record = { }} class="grid grid-cols-2 gap-2 sm:grid-cols-4"> - - + + default
- - + +
@@ -78,15 +79,15 @@ let editingId = $state(null); order: {r.displayOrder}
- +
+ if (!confirm(t("roles.delete_confirm", { key: r.key }))) e.preventDefault(); + }}>{t("common.delete")}
@@ -95,11 +96,11 @@ let editingId = $state(null); {/each}
{:else} -

등록된 role 이 없습니다.

+

{t("roles.empty")}

{/if}
- + @@ -107,7 +108,7 @@ let editingId = $state(null); - +
diff --git a/src/routes/admin/parts/+page.svelte b/src/routes/admin/parts/+page.svelte index 1ca03aa..8ca1c92 100644 --- a/src/routes/admin/parts/+page.svelte +++ b/src/routes/admin/parts/+page.svelte @@ -53,7 +53,7 @@ function teamLabel(tm: { name: string; departmentName: string | null }) { type="text" name="name" required - placeholder="예: iOS파트" + placeholder={t("parts.name_placeholder")} class="mt-1 w-full rounded-md border border-gray-300 px-3 py-1.5 text-sm focus:border-blue-500 focus:outline-none" />
@@ -62,7 +62,7 @@ function teamLabel(tm: { name: string; departmentName: string | null }) { id="part-code" type="text" name="code" - placeholder="예: IOS" + placeholder={t("parts.code_placeholder")} class="mt-1 w-full rounded-md border border-gray-300 px-3 py-1.5 text-sm focus:border-blue-500 focus:outline-none" />
@@ -121,8 +121,19 @@ function teamLabel(tm: { name: string; departmentName: string | null }) { }} class="grid grid-cols-2 gap-2 sm:grid-cols-4"> - - + + - + diff --git a/src/routes/admin/saml-sps/+page.svelte b/src/routes/admin/saml-sps/+page.svelte index 72c6813..a1f7e3e 100644 --- a/src/routes/admin/saml-sps/+page.svelte +++ b/src/routes/admin/saml-sps/+page.svelte @@ -36,7 +36,7 @@ function parseSpMetadata() { const doc = parser.parseFromString(xml, "text/xml"); const parseErr = doc.querySelector("parsererror"); - if (parseErr) throw new Error("XML 파싱 오류"); + if (parseErr) throw new Error(t("saml.parse_error_xml")); const entityId = doc.documentElement.getAttribute("entityID") ?? ""; if (entityId) createEntityId = entityId; @@ -73,7 +73,7 @@ function parseSpMetadata() { } } } catch (e) { - metaParseError = e instanceof Error ? e.message : "파싱 실패"; + metaParseError = e instanceof Error ? e.message : t("saml.parse_error_generic"); } } diff --git a/src/routes/admin/saml-sps/[id]/+page.svelte b/src/routes/admin/saml-sps/[id]/+page.svelte index 1b97922..fe589d5 100644 --- a/src/routes/admin/saml-sps/[id]/+page.svelte +++ b/src/routes/admin/saml-sps/[id]/+page.svelte @@ -2,6 +2,7 @@ import { enhance } from "$app/forms"; import { resolve } from "$app/paths"; import type { ActionData, PageData } from "./$types"; +import { t } from "$lib/i18n.svelte"; const { data, form } = $props<{ data: PageData; form?: ActionData }>(); @@ -12,7 +13,7 @@ let editingId = $state(null);
- ← SAML SP + ← {t("saml.title")}

{data.sp.name}

{data.sp.entityId}
@@ -23,8 +24,8 @@ let editingId = $state(null);
-

Role 목록

- {data.roles.length} 개 +

{t("roles.list_title")}

+ {t("roles.count", { count: data.roles.length })}
{#if data.roles.length > 0} @@ -63,8 +64,8 @@ let editingId = $state(null); default
- - + +
@@ -78,15 +79,15 @@ let editingId = $state(null); order: {r.displayOrder}
- +
+ if (!confirm(t("roles.delete_confirm", { key: r.key }))) e.preventDefault(); + }}>{t("common.delete")}
@@ -95,11 +96,11 @@ let editingId = $state(null); {/each} {:else} -

등록된 role 이 없습니다.

+

{t("roles.empty")}

{/if}
- + @@ -107,7 +108,7 @@ let editingId = $state(null); - + diff --git a/src/routes/admin/skins/+page.svelte b/src/routes/admin/skins/+page.svelte index e5e8f74..c56aedb 100644 --- a/src/routes/admin/skins/+page.svelte +++ b/src/routes/admin/skins/+page.svelte @@ -56,7 +56,7 @@ function clientLabel(clientType: string, clientRefId: string): string { {/if} {#if (form as { updated?: boolean } | null)?.updated} -
스킨이 수정되었습니다. (캐시도 자동으로 초기화되었습니다.)
+
{t("skins.updated_success")}
{/if} {#if showCreate} diff --git a/src/routes/admin/skins/guide/+page.svelte b/src/routes/admin/skins/guide/+page.svelte index 0292569..dece0e6 100644 --- a/src/routes/admin/skins/guide/+page.svelte +++ b/src/routes/admin/skins/guide/+page.svelte @@ -68,7 +68,7 @@ import { t } from "$lib/i18n.svelte";

{t("skins.guide_auth_title")}

{t("skins.guide_auth_desc")}

-
{`// 스킨 서버에서 요청 검증 예시 (Node.js)
+            
{`${t("skins.guide_code_comment")}
 app.get('/login-skin.html', (req, res) => {
   const token = req.headers['x-idp-token'];
   if (token !== process.env.SKIN_SECRET) {
@@ -89,7 +89,7 @@ app.get('/login-skin.html', (req, res) => {
 
   
   
-  로그인
+  ${t("skins.guide_example_login")}