Feat add iam auth
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
-- Revocable admin sessions.
|
||||
--
|
||||
-- Until now an admin-panel token was purely self-contained: once signed it was
|
||||
-- valid for its full 12h and nothing could cut it short — not deactivating the
|
||||
-- account, not a central "sign out everywhere". Connecting the central IAM made
|
||||
-- that gap concrete, because an IAM session revocation had no effect on a token
|
||||
-- this API had already issued.
|
||||
--
|
||||
-- Each issued token now carries a `jti` recorded here, and JwtStrategy rejects a
|
||||
-- token whose row is revoked. Rows are only written (and only enforced) while
|
||||
-- session tracking is enabled, so a deployment that leaves it off keeps exactly
|
||||
-- the previous behaviour and the previous query count.
|
||||
CREATE TABLE "AdminSession" (
|
||||
"id" TEXT NOT NULL,
|
||||
"jti" TEXT NOT NULL,
|
||||
"adminUserId" TEXT NOT NULL,
|
||||
"iamSessionId" TEXT,
|
||||
"iamSub" TEXT,
|
||||
"ipAddress" TEXT,
|
||||
"userAgent" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"expiresAt" TIMESTAMP(3),
|
||||
"lastSeenAt" TIMESTAMP(3),
|
||||
"revokedAt" TIMESTAMP(3),
|
||||
"revokedReason" TEXT,
|
||||
|
||||
CONSTRAINT "AdminSession_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX "AdminSession_jti_key" ON "AdminSession"("jti");
|
||||
CREATE INDEX "AdminSession_adminUserId_idx" ON "AdminSession"("adminUserId");
|
||||
CREATE INDEX "AdminSession_iamSessionId_idx" ON "AdminSession"("iamSessionId");
|
||||
CREATE INDEX "AdminSession_iamSub_idx" ON "AdminSession"("iamSub");
|
||||
CREATE INDEX "AdminSession_expiresAt_idx" ON "AdminSession"("expiresAt");
|
||||
|
||||
ALTER TABLE "AdminSession"
|
||||
ADD CONSTRAINT "AdminSession_adminUserId_fkey"
|
||||
FOREIGN KEY ("adminUserId") REFERENCES "AdminUser"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@@ -0,0 +1,33 @@
|
||||
-- Revocable app sessions (customer app and sales-expert app).
|
||||
--
|
||||
-- The admin panel got this in 20260925120000_admin_session; the app token family
|
||||
-- had the same gap: a 30-day self-contained token that nothing could end early.
|
||||
-- That matters more here, because the app's login is OTP-only — there is no
|
||||
-- password an operator could change to cut access.
|
||||
--
|
||||
-- No foreign key on purpose: an app token's subject is a Customer for a customer
|
||||
-- and an AdminUser for a sales expert (D21), which one FK cannot express.
|
||||
CREATE TABLE "AppSession" (
|
||||
"id" TEXT NOT NULL,
|
||||
"jti" TEXT NOT NULL,
|
||||
"subjectId" TEXT NOT NULL,
|
||||
"subjectType" TEXT NOT NULL,
|
||||
"mobile" TEXT,
|
||||
"iamSessionId" TEXT,
|
||||
"iamSub" TEXT,
|
||||
"ipAddress" TEXT,
|
||||
"userAgent" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"expiresAt" TIMESTAMP(3),
|
||||
"lastSeenAt" TIMESTAMP(3),
|
||||
"revokedAt" TIMESTAMP(3),
|
||||
"revokedReason" TEXT,
|
||||
|
||||
CONSTRAINT "AppSession_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX "AppSession_jti_key" ON "AppSession"("jti");
|
||||
CREATE INDEX "AppSession_subjectId_idx" ON "AppSession"("subjectId");
|
||||
CREATE INDEX "AppSession_iamSessionId_idx" ON "AppSession"("iamSessionId");
|
||||
CREATE INDEX "AppSession_iamSub_idx" ON "AppSession"("iamSub");
|
||||
CREATE INDEX "AppSession_expiresAt_idx" ON "AppSession"("expiresAt");
|
||||
@@ -184,6 +184,11 @@ model AdminUser {
|
||||
/// Only ever set for role = SALES_EXPERT (D17).
|
||||
salesExpertProfile SalesExpertProfile?
|
||||
|
||||
/// Issued admin-panel tokens, recorded only while session tracking is on
|
||||
/// (AUTH_SESSION_TRACKING, or implicitly whenever the IAM is enabled) so a
|
||||
/// token can be revoked before it expires. See AdminSession.
|
||||
sessions AdminSession[]
|
||||
|
||||
@@index([role])
|
||||
@@index([userTypeId])
|
||||
@@index([avatarFileId])
|
||||
@@ -191,6 +196,94 @@ model AdminUser {
|
||||
@@index([lastName])
|
||||
}
|
||||
|
||||
/// An issued APP token (customer app / sales-expert app), so it can be revoked
|
||||
/// before it expires — the counterpart of AdminSession for the other token family.
|
||||
///
|
||||
/// WHY A SEPARATE TABLE AND NO FOREIGN KEY
|
||||
/// ---------------------------------------
|
||||
/// An app token's `sub` is a Customer id for a customer and an **AdminUser id**
|
||||
/// for a sales expert (D21). One FK cannot point at two tables, so the subject is
|
||||
/// stored as a plain id plus its type. The rows are short-lived operational data,
|
||||
/// not part of the accounting model, so the missing FK costs nothing here.
|
||||
model AppSession {
|
||||
id String @id @default(cuid())
|
||||
|
||||
/// The token's `jti` claim. Unique: one row per issued token.
|
||||
jti String @unique
|
||||
|
||||
/// Customer.id, or AdminUser.id when subjectType is "sales-expert".
|
||||
subjectId String
|
||||
subjectType String
|
||||
|
||||
mobile String?
|
||||
|
||||
/// Set when the login was confirmed by the central IAM: its session id (`sid`)
|
||||
/// and stable user id (`sub`), which is what a back-channel logout arrives with.
|
||||
iamSessionId String?
|
||||
iamSub String?
|
||||
|
||||
ipAddress String?
|
||||
userAgent String?
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
expiresAt DateTime?
|
||||
lastSeenAt DateTime?
|
||||
|
||||
revokedAt DateTime?
|
||||
revokedReason String?
|
||||
|
||||
@@index([subjectId])
|
||||
@@index([iamSessionId])
|
||||
@@index([iamSub])
|
||||
@@index([expiresAt])
|
||||
}
|
||||
|
||||
/// An issued admin-panel JWT, so it can be revoked before it expires.
|
||||
///
|
||||
/// WHY THIS EXISTS
|
||||
/// ---------------
|
||||
/// The admin token is self-contained and valid for 12h, which means nothing —
|
||||
/// not disabling the account, not a central "sign out everywhere" — could cut a
|
||||
/// session short: the token kept working until it expired. With the central IAM
|
||||
/// in the loop that gap became visible (an IAM session revocation had no effect
|
||||
/// here), so every issued token now carries a `jti` recorded on this table and
|
||||
/// JwtStrategy refuses a token whose row is revoked.
|
||||
///
|
||||
/// Rows are written ONLY while tracking is enabled, and JwtStrategy only
|
||||
/// enforces them while it is enabled, so a deployment that does not want the
|
||||
/// extra lookup behaves exactly as before.
|
||||
model AdminSession {
|
||||
id String @id @default(cuid())
|
||||
|
||||
/// The token's `jti` claim. Unique: one row per issued token.
|
||||
jti String @unique
|
||||
|
||||
adminUserId String
|
||||
adminUser AdminUser @relation(fields: [adminUserId], references: [id], onDelete: Cascade)
|
||||
|
||||
/// Set when the login was verified by the IAM: its session id (`sid`) and the
|
||||
/// stable user id (`sub`) there. Back-channel logout arrives keyed by these —
|
||||
/// deliberately here rather than as columns on AdminUser, so enabling the
|
||||
/// feature needs no change to an existing table.
|
||||
iamSessionId String?
|
||||
iamSub String?
|
||||
|
||||
ipAddress String?
|
||||
userAgent String?
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
expiresAt DateTime?
|
||||
lastSeenAt DateTime?
|
||||
|
||||
revokedAt DateTime?
|
||||
revokedReason String?
|
||||
|
||||
@@index([adminUserId])
|
||||
@@index([iamSessionId])
|
||||
@@index([iamSub])
|
||||
@@index([expiresAt])
|
||||
}
|
||||
|
||||
/// The sales-expert fields legacy kept on the `customers` row (type=1) and that
|
||||
/// were lost when a sales expert became an AdminUser (D17).
|
||||
///
|
||||
|
||||
Reference in New Issue
Block a user