{"version":3,"file":"session-native-login.mjs","names":[],"sources":["../../src/internal/session-native-login.ts"],"sourcesContent":["import type { LifecycleHooks } from \"@yielded/auth/Hooks\";\nimport { AuthenticationClock } from \"@yielded/auth/Operations\";\nimport {\n  PendingAuthenticationInvalid,\n  StaleAuthentication,\n  SessionConflict,\n  type PendingAuthentication,\n  type PendingAuthenticationRecord,\n  type SessionUnavailable,\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 type * as SqlClient from \"effect/sql/SqlClient\";\n\nimport { sqlBatchAssertion } from \"./d1-planning\";\nimport { PersistenceMappingError, isMappedConstraintConflict } from \"./mapping-error\";\nimport type { PendingAuthenticationMapping } from \"./models/session-model\";\nimport type { NativeSqlTables } from \"./native-sql-table\";\nimport { prepareNativeSession, type StoredSessionPending } from \"./session-native-pending\";\nimport { makeNativeSessionPendingReader } from \"./session-native-pending-read\";\nimport {\n  normalizeSessionOperation,\n  sessionUnavailable,\n  sameSessionRevision,\n  assessSessionAt,\n  invalidSessionState,\n} from \"./session-native-state\";\nimport { allocateSessionValue } from \"./session-policy\";\nimport {\n  CurrentSqlCommit,\n  makeSqlCommitExecutor,\n  SqlBatchCommit,\n  registerSqlPostcondition,\n  registerSqlBatchPostcondition,\n  appendSqlBatchStatement,\n} from \"./sql-commit\";\nimport type { AnyTableModel } from \"./table-model\";\n\nexport type NativePendingAuthenticationMapping<Claims> = PendingAuthenticationMapping<\n  Claims,\n  AnyTableModel,\n  AnyTableModel,\n  AnyTableModel,\n  unknown\n>;\n\nexport const makeNativePendingAuthenticationServices = Effect.fnUntraced(function* <Claims>(\n  tables: NativeSqlTables,\n  mapping: NativePendingAuthenticationMapping<Claims>,\n): Effect.fn.Return<\n  { readonly pendingAuthentication: PendingAuthentication<Claims> },\n  SessionUnavailable,\n  SqlClient.SqlClient | LifecycleHooks | SqlBatchCommit\n> {\n  const batch = yield* SqlBatchCommit;\n\n  const {\n    state,\n    pending,\n    read: readPending,\n  } = yield* normalizeSessionOperation(\n    makeNativeSessionPendingReader(tables, mapping, batch !== undefined),\n  );\n\n  const executor = yield* makeSqlCommitExecutor(sessionUnavailable);\n  const external = yield* Effect.serviceOption(CurrentSqlCommit);\n\n  const { sql, now } = state,\n    { p, table } = pending;\n\n  const native = <A, E, R>(\n    work: Effect.Effect<A, E, R>,\n    mode: \"transaction\" | \"statement\" = \"transaction\",\n  ) =>\n    (batch === undefined\n      ? executor.operation(normalizeSessionOperation(work), mode)\n      : executor\n          .operationBatch(normalizeSessionOperation(work))\n          .pipe(Effect.provideService(SqlBatchCommit, batch))\n    ).pipe(Effect.provideService(AuthenticationClock, state.clockPolicy));\n\n  const verifyPayload = Effect.fnUntraced(function* (stored: StoredSessionPending) {\n    const record = yield* mapping.login.decode(stored.snapshot);\n\n    if (\n      !(\n        record.digest === stored.digest &&\n        record.version === stored.version &&\n        record.evidence.flowId === stored.flowId &&\n        record.evidence.bindingDigest === stored.bindingDigest &&\n        record.evidence.revision.subjectId === stored.subjectId &&\n        DateTime.toEpochMillis(record.expiresAt) === stored.expiresAtMillis &&\n        record.attemptLimit === stored.attemptLimit\n      )\n    )\n      return yield* invalidSessionState();\n\n    return record;\n  });\n\n  const read = Effect.fnUntraced(function* (\n    digest: Parameters<PendingAuthentication<Claims>[\"read\"]>[0][\"digest\"],\n  ) {\n    const selected = yield* readPending(\"Login\", digest);\n\n    if (selected === undefined) return undefined;\n    const record = yield* verifyPayload(selected.record);\n\n    if (!sameSessionRevision(selected.authority.revision, record.evidence.revision))\n      return undefined;\n\n    return { record, requirement: selected.authority.requirement };\n  });\n\n  const pendingAuthentication: PendingAuthentication<Claims> = {\n    create: (input, _now, project) =>\n      native(\n        Effect.gen(function* () {\n          const current = yield* state.read(input.evidence.revision.subjectId, true);\n\n          if (\n            current === undefined ||\n            !sameSessionRevision(current.revision, input.evidence.revision)\n          )\n            return yield* StaleAuthentication.make({});\n          yield* assessSessionAt(input.evidence, current.requirement, current.now);\n          const expires = DateTime.toEpochMillis(input.expiresAt);\n\n          if (expires <= current.now) return yield* StaleAuthentication.make({});\n\n          const version = yield* allocateSessionValue(\n            \"interactive\",\n            mapping.pending.allocateVersion,\n            mapping.pending.allocateVersionSync,\n          );\n\n          const record: PendingAuthenticationRecord<Claims> = { ...input, version };\n          const snapshot = yield* mapping.login.encode(record);\n\n          yield* state.guardPolicy(current);\n          const condition = sql`${state.authorityCondition(current.revision, current.native)} and ${now} >= ${current.now} and ${now} < ${expires}`;\n\n          if (batch !== undefined)\n            yield* appendSqlBatchStatement(sqlBatchAssertion(sql, condition));\n          yield* pending\n            .insert(\n              {\n                moduleId: mapping.moduleId,\n                kind: \"Login\",\n                digest: record.digest,\n                version,\n                flowId: record.evidence.flowId,\n                subjectId: current.native,\n                bindingDigest: record.evidence.bindingDigest,\n                snapshot,\n                expiresAt: record.expiresAt,\n                attemptLimit: record.attemptLimit,\n              },\n              condition,\n            )\n            .pipe(\n              Effect.catchCause((cause) =>\n                Effect.failCause<SessionConflict | Cause.Cause.Error<typeof cause>>(\n                  isMappedConstraintConflict(mapping.isConstraintConflict, cause)\n                    ? Cause.map(cause, () => SessionConflict.make({}))\n                    : cause,\n                ),\n              ),\n            );\n          const final = sql`${condition} and exists(select 1 from ${table.name} where ${pending.predicate(table, \"Login\", record.digest)} and ${state.exact(table, p.version, version)} and ${state.exact(table, p.snapshot, snapshot)})`;\n\n          if (batch !== undefined)\n            yield* registerSqlBatchPostcondition({\n              name: \"pending-login-issuance\",\n              statement: sqlBatchAssertion(sql, final),\n            });\n          else if (Option.isSome(external))\n            yield* registerSqlPostcondition({\n              name: \"pending-login-issuance\",\n              check: Effect.gen(function* () {\n                const rows = yield* sql`select 1 where ${final}`;\n\n                if (!(rows.length === 1)) 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          return yield* prepareNativeSession(record, project);\n        }),\n      ),\n    read: ({ digest }) =>\n      executor.read(read(digest)).pipe(\n        Effect.filterOrFail(\n          (result) => !(result === undefined),\n          () => PendingAuthenticationInvalid.make({}),\n        ),\n      ),\n    reject: ({ digest }, project) =>\n      native(\n        Effect.gen(function* () {\n          yield* pending.reject(\"Login\", digest);\n\n          return yield* prepareNativeSession({ _tag: \"Rejected\" } as const, project);\n        }),\n        \"statement\",\n      ),\n  };\n\n  return { pendingAuthentication };\n});\n"],"mappings":"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"}