{"version":3,"file":"session-native-validity.mjs","names":[],"sources":["../../src/internal/session-native-validity.ts"],"sourcesContent":["import type { LifecycleHooks } from \"@yielded/auth/Hooks\";\nimport { AuthenticationClock } from \"@yielded/auth/Operations\";\nimport {\n  SessionInvalid,\n  StaleAuthentication,\n  type SignedSessionValidity,\n} from \"@yielded/auth/Sessions\";\nimport * as Cause from \"effect/Cause\";\nimport * as DateTime from \"effect/DateTime\";\nimport * as Effect from \"effect/Effect\";\nimport * as Option from \"effect/Option\";\nimport * as SqlClient from \"effect/sql/SqlClient\";\nimport type * as Statement from \"effect/sql/Statement\";\n\nimport { sqlBatchAssertion } from \"./d1-planning\";\nimport { PersistenceMappingError } from \"./mapping-error\";\nimport type { SignedSessionValidityMapping } from \"./models/session-model\";\nimport type { NativeSqlTables, SqlTable } from \"./native-sql-table\";\nimport { prepareNativeSession } from \"./session-native-pending\";\nimport {\n  normalizeSessionOperation,\n  invalidSessionState,\n  sessionUnavailable,\n} from \"./session-native-state\";\nimport { allocateSessionValue } from \"./session-policy\";\nimport { exactSqlText, executeSqlChange } from \"./sql-change\";\nimport {\n  CurrentSqlCommit,\n  makeSqlCommitExecutor,\n  SqlBatchCommit,\n  appendSqlBatchStatement,\n  registerSqlBatchPostcondition,\n  registerSqlPostcondition,\n} from \"./sql-commit\";\nimport type { AnyTableModel } from \"./table-model\";\n\nexport type NativeSignedSessionValidityMapping = SignedSessionValidityMapping<\n  AnyTableModel,\n  AnyTableModel,\n  unknown,\n  unknown\n>;\n\nexport const makeNativeSignedSessionValidityServices = Effect.fnUntraced(function* (\n  tables: NativeSqlTables,\n  mapping: NativeSignedSessionValidityMapping,\n): Effect.fn.Return<\n  { readonly signedSessionValidity: SignedSessionValidity },\n  never,\n  SqlClient.SqlClient | LifecycleHooks | SqlBatchCommit\n> {\n  const batch = yield* SqlBatchCommit;\n  const { futureToleranceMillis } = yield* AuthenticationClock;\n\n  const sql = (yield* SqlClient.SqlClient).withoutTransforms(),\n    executor = yield* makeSqlCommitExecutor(sessionUnavailable);\n\n  const external = yield* Effect.serviceOption(CurrentSqlCommit);\n\n  const s = mapping.subject,\n    t = mapping.tombstone,\n    subject = tables(s.table),\n    tombstone = tables(t.table),\n    now = tables.expression(mapping.clock.engineNowMillis);\n\n  const id = (table: SqlTable, key: string, value: unknown) =>\n    sql`${table.column(key)} = ${table.value(key, value)}`;\n\n  const exact = (table: SqlTable, key: string, value: unknown) =>\n    exactSqlText(sql, table.column(key), table.value(key, value));\n\n  const key = (nativeSubject: unknown, nativeSession: unknown) =>\n    sql.and([\n      exact(tombstone, t.moduleId, mapping.moduleId),\n      id(tombstone, t.subjectId, nativeSubject),\n      id(tombstone, t.sessionId, nativeSession),\n    ]);\n\n  const native = <A, E, R>(work: Effect.Effect<A, E, R>) =>\n    batch === undefined\n      ? executor.operation(normalizeSessionOperation(work), \"statement\")\n      : executor\n          .operationBatch(normalizeSessionOperation(work))\n          .pipe(Effect.provideService(SqlBatchCommit, batch));\n\n  const stage = Effect.fnUntraced(function* (statement: Statement.Fragment, expected?: number) {\n    if (batch !== undefined) {\n      yield* appendSqlBatchStatement(sql`${statement}`);\n      if (expected !== undefined)\n        yield* appendSqlBatchStatement(sqlBatchAssertion(sql, sql`changes() = ${expected}`));\n\n      return expected;\n    }\n\n    return yield* executeSqlChange(sql, statement);\n  });\n\n  const final = Effect.fnUntraced(function* (name: string, condition: Statement.Fragment) {\n    if (batch !== undefined)\n      yield* registerSqlBatchPostcondition({ name, statement: sqlBatchAssertion(sql, condition) });\n    else if (Option.isSome(external))\n      yield* registerSqlPostcondition({\n        name,\n        check: Effect.gen(function* () {\n          if (!((yield* sql`select 1 where ${condition}`).length === 1))\n            return yield* invalidSessionState();\n        }).pipe(\n          Effect.catchCause((cause) =>\n            Effect.failCause(\n              Cause.map(cause, (error) =>\n                PersistenceMappingError.make({ operation: \"decode\", cause: error }),\n              ),\n            ),\n          ),\n        ),\n      });\n  });\n\n  const signedSessionValidity: SignedSessionValidity = {\n    verify: (record) =>\n      executor\n        .verify(\n          Effect.gen(function* () {\n            const nativeSubject = yield* mapping.subjectId.toNative(record.subjectId),\n              nativeSession = yield* mapping.sessionId.toNative(record.sessionId);\n\n            const rows =\n              yield* sql`select ${subject.fields(\"subject_\")} from ${subject.name} where ${id(subject, s.id, nativeSubject)} and ${id(subject, s.status, s.activeStatusValue)} and ${exact(subject, s.securityRevision, record.securityRevision)} and ${DateTime.toEpochMillis(record.issuedAt)} <= ${now} + ${futureToleranceMillis} and ${DateTime.toEpochMillis(record.issuedAt)} < ${DateTime.toEpochMillis(record.expiresAt)} and ${now} < ${DateTime.toEpochMillis(record.expiresAt)} and ${DateTime.toEpochMillis(record.expiresAt)} <= ${DateTime.toEpochMillis(record.absoluteExpiresAt)} and not exists(select 1 from ${tombstone.name} where ${key(nativeSubject, nativeSession)}) limit 2`;\n\n            if (!(rows.length <= 1)) return yield* invalidSessionState();\n            if (rows[0] === undefined) return false;\n            const decoded = subject.decode(rows[0], \"subject_\");\n\n            if (\n              !(\n                (yield* mapping.subjectId.toSubject(decoded[s.id])) === record.subjectId &&\n                s.isActiveStatus(decoded[s.status])\n              )\n            )\n              return yield* invalidSessionState();\n\n            return true;\n          }),\n        )\n        .pipe(\n          Effect.flatMap((valid) => (valid ? Effect.void : Effect.fail(SessionInvalid.make({})))),\n        ),\n    revoke: (input, project) =>\n      native(\n        Effect.gen(function* () {\n          const nativeSubject = yield* mapping.subjectId.toNative(input.subjectId),\n            nativeSession = yield* mapping.sessionId.toNative(input.sessionId);\n\n          const row = {\n            ...t.encodeInsert({\n              moduleId: mapping.moduleId,\n              subjectId: nativeSubject,\n              sessionId: nativeSession,\n              absoluteExpiresAt: input.absoluteExpiresAt,\n            }),\n            [t.moduleId]: mapping.moduleId,\n            [t.subjectId]: nativeSubject,\n            [t.sessionId]: nativeSession,\n            [t.absoluteExpiresAt]: t.encodeInstant(input.absoluteExpiresAt),\n          };\n\n          const expiry = tables.expression(\n            mapping.clock.toMillis(tombstone.column(t.absoluteExpiresAt)),\n          );\n\n          const next = DateTime.toEpochMillis(input.absoluteExpiresAt);\n          const longest = sql`case when ${expiry} >= ${next} then ${tombstone.column(t.absoluteExpiresAt)} else ${tombstone.value(t.absoluteExpiresAt, row[t.absoluteExpiresAt])} end`;\n          const insert = tombstone.insert(row);\n\n          const statement = sql.onDialectOrElse({\n            mysql: () =>\n              sql`${insert} on duplicate key update ${tombstone.columnName(t.absoluteExpiresAt)} = ${longest}`,\n            orElse: () =>\n              sql`${insert} on conflict (${tombstone.columnName(t.moduleId)}, ${tombstone.columnName(t.subjectId)}, ${tombstone.columnName(t.sessionId)}) do update set ${tombstone.columnName(t.absoluteExpiresAt)} = ${longest}`,\n          });\n\n          yield* stage(statement);\n          yield* final(\n            \"assisted-session-revocation\",\n            sql`exists(select 1 from ${tombstone.name} where ${key(nativeSubject, nativeSession)} and ${expiry} >= ${next})`,\n          );\n\n          return yield* prepareNativeSession(undefined, project);\n        }),\n      ),\n    revokeAll: (input, project) =>\n      native(\n        Effect.gen(function* () {\n          const nativeSubject = yield* mapping.subjectId.toNative(input.subjectId);\n\n          const next = yield* allocateSessionValue(\n            \"interactive\",\n            s.nextSecurityRevision?.(input.expectedSecurityRevision),\n            s.nextSecurityRevisionSync === undefined\n              ? undefined\n              : () => s.nextSecurityRevisionSync!(input.expectedSecurityRevision),\n          );\n\n          if (!(next !== input.expectedSecurityRevision)) return yield* invalidSessionState();\n\n          const changed = yield* stage(\n            sql`${subject.update({ [s.securityRevision]: next })} where ${id(subject, s.id, nativeSubject)} and ${id(subject, s.status, s.activeStatusValue)} and ${exact(subject, s.securityRevision, input.expectedSecurityRevision)}`,\n            1,\n          );\n\n          if (changed !== 1) return yield* StaleAuthentication.make({});\n          yield* final(\n            \"all-assisted-session-revocation\",\n            sql`exists(select 1 from ${subject.name} where ${id(subject, s.id, nativeSubject)} and ${exact(subject, s.securityRevision, next)})`,\n          );\n\n          return yield* prepareNativeSession(undefined, project);\n        }),\n      ),\n  };\n\n  return { signedSessionValidity };\n});\n"],"mappings":"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"}