@yielded/auth-persistence 0.1.0-beta.23 → 0.1.0-beta.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (182) hide show
  1. package/README.md +11 -0
  2. package/dist/Adapter.d.mts +12 -6
  3. package/dist/Adapter.mjs +8 -3
  4. package/dist/OAuthPersistence.d.mts +87 -0
  5. package/dist/OAuthPersistence.mjs +125 -0
  6. package/dist/OAuthPersistence.mjs.map +1 -0
  7. package/dist/_virtual/_rolldown/runtime.mjs +13 -0
  8. package/dist/index.d.mts +3 -1
  9. package/dist/index.mjs +3 -1
  10. package/dist/internal/configuration.d.mts +1 -1
  11. package/dist/internal/configuration.mjs.map +1 -1
  12. package/dist/internal/email-kernel.d.mts +1 -1
  13. package/dist/internal/email-kernel.mjs +1 -1
  14. package/dist/internal/email-kernel.mjs.map +1 -1
  15. package/dist/internal/models/oauth-connected-model.d.mts +294 -0
  16. package/dist/internal/models/oauth-connected-model.mjs +22 -0
  17. package/dist/internal/models/oauth-connected-model.mjs.map +1 -0
  18. package/dist/internal/models/oauth-model.d.mts +467 -0
  19. package/dist/internal/models/oauth-model.mjs +49 -0
  20. package/dist/internal/models/oauth-model.mjs.map +1 -0
  21. package/dist/internal/models/password-model.d.mts +6 -48
  22. package/dist/internal/models/password-model.mjs +0 -1
  23. package/dist/internal/models/password-model.mjs.map +1 -1
  24. package/dist/internal/native-sql-table.d.mts +4 -0
  25. package/dist/internal/native-sql-table.mjs +7 -0
  26. package/dist/internal/native-sql-table.mjs.map +1 -1
  27. package/dist/internal/oauth/accounts.d.mts +8 -0
  28. package/dist/internal/oauth/accounts.mjs +379 -0
  29. package/dist/internal/oauth/accounts.mjs.map +1 -0
  30. package/dist/internal/oauth/connected-access.d.mts +9 -0
  31. package/dist/internal/oauth/connected-access.mjs +229 -0
  32. package/dist/internal/oauth/connected-access.mjs.map +1 -0
  33. package/dist/internal/oauth/connected-collection.d.mts +7 -0
  34. package/dist/internal/oauth/connected-collection.mjs +214 -0
  35. package/dist/internal/oauth/connected-collection.mjs.map +1 -0
  36. package/dist/internal/oauth/connected-custody.d.mts +6 -0
  37. package/dist/internal/oauth/connected-custody.mjs +165 -0
  38. package/dist/internal/oauth/connected-custody.mjs.map +1 -0
  39. package/dist/internal/oauth/connected-flow.d.mts +7 -0
  40. package/dist/internal/oauth/connected-flow.mjs +324 -0
  41. package/dist/internal/oauth/connected-flow.mjs.map +1 -0
  42. package/dist/internal/oauth/connected-input.mjs +122 -0
  43. package/dist/internal/oauth/connected-input.mjs.map +1 -0
  44. package/dist/internal/oauth/connected-keys.d.mts +2 -0
  45. package/dist/internal/oauth/connected-keys.mjs +22 -0
  46. package/dist/internal/oauth/connected-keys.mjs.map +1 -0
  47. package/dist/internal/oauth/connected-maintenance.d.mts +9 -0
  48. package/dist/internal/oauth/connected-maintenance.mjs +226 -0
  49. package/dist/internal/oauth/connected-maintenance.mjs.map +1 -0
  50. package/dist/internal/oauth/connected-management.d.mts +9 -0
  51. package/dist/internal/oauth/connected-management.mjs +243 -0
  52. package/dist/internal/oauth/connected-management.mjs.map +1 -0
  53. package/dist/internal/oauth/connected-reference.mjs +63 -0
  54. package/dist/internal/oauth/connected-reference.mjs.map +1 -0
  55. package/dist/internal/oauth/connected-settlement.d.mts +8 -0
  56. package/dist/internal/oauth/connected-settlement.mjs +141 -0
  57. package/dist/internal/oauth/connected-settlement.mjs.map +1 -0
  58. package/dist/internal/oauth/connected-sign-in.d.mts +10 -0
  59. package/dist/internal/oauth/connected-sign-in.mjs +281 -0
  60. package/dist/internal/oauth/connected-sign-in.mjs.map +1 -0
  61. package/dist/internal/oauth/connected-state.d.mts +14 -0
  62. package/dist/internal/oauth/connected-state.mjs +469 -0
  63. package/dist/internal/oauth/connected-state.mjs.map +1 -0
  64. package/dist/internal/oauth/connected-target.d.mts +21 -0
  65. package/dist/internal/oauth/connected-target.mjs +117 -0
  66. package/dist/internal/oauth/connected-target.mjs.map +1 -0
  67. package/dist/internal/oauth/flow.d.mts +5 -0
  68. package/dist/internal/oauth/flow.mjs +279 -0
  69. package/dist/internal/oauth/flow.mjs.map +1 -0
  70. package/dist/internal/oauth/input.mjs +127 -0
  71. package/dist/internal/oauth/input.mjs.map +1 -0
  72. package/dist/internal/oauth/owner.d.mts +10 -0
  73. package/dist/internal/oauth/owner.mjs +10 -0
  74. package/dist/internal/oauth/owner.mjs.map +1 -0
  75. package/dist/internal/oauth/query-compiler.d.mts +7 -0
  76. package/dist/internal/oauth/query-compiler.mjs +9 -0
  77. package/dist/internal/oauth/query-compiler.mjs.map +1 -0
  78. package/dist/internal/oauth/registration.d.mts +7 -0
  79. package/dist/internal/oauth/registration.mjs +393 -0
  80. package/dist/internal/oauth/registration.mjs.map +1 -0
  81. package/dist/internal/oauth/state.mjs +107 -0
  82. package/dist/internal/oauth/state.mjs.map +1 -0
  83. package/dist/internal/oauth/target.d.mts +31 -0
  84. package/dist/internal/oauth/target.mjs +273 -0
  85. package/dist/internal/oauth/target.mjs.map +1 -0
  86. package/dist/internal/oauth-kernel.d.mts +1423 -0
  87. package/dist/internal/oauth-kernel.mjs +187 -0
  88. package/dist/internal/oauth-kernel.mjs.map +1 -0
  89. package/dist/internal/oauth-proxy.d.mts +31 -0
  90. package/dist/internal/oauth-proxy.mjs +129 -0
  91. package/dist/internal/oauth-proxy.mjs.map +1 -0
  92. package/dist/internal/passkey/write-state.mjs +9 -1
  93. package/dist/internal/passkey/write-state.mjs.map +1 -1
  94. package/dist/internal/passkey-kernel.d.mts +9 -9
  95. package/dist/internal/{password-attempts.d.mts → password-credentials.d.mts} +3 -4
  96. package/dist/internal/password-credentials.mjs +121 -0
  97. package/dist/internal/password-credentials.mjs.map +1 -0
  98. package/dist/internal/password-kernel.d.mts +8 -24
  99. package/dist/internal/password-kernel.mjs +5 -43
  100. package/dist/internal/password-kernel.mjs.map +1 -1
  101. package/dist/internal/persistence.d.mts +1 -1
  102. package/dist/internal/persistence.mjs +2 -2
  103. package/dist/internal/persistence.mjs.map +1 -1
  104. package/dist/internal/query-operations.d.mts +2 -0
  105. package/dist/internal/session-kernel.d.mts +2 -2
  106. package/dist/internal/session-kernel.mjs +74 -18
  107. package/dist/internal/session-kernel.mjs.map +1 -1
  108. package/dist/internal/sql-oauth-connected-model.d.mts +29 -0
  109. package/dist/internal/sql-oauth-connected-model.mjs +7 -0
  110. package/dist/internal/sql-oauth-connected-model.mjs.map +1 -0
  111. package/dist/internal/sql-oauth-model.d.mts +60 -0
  112. package/dist/internal/sql-oauth-model.mjs +10 -0
  113. package/dist/internal/sql-oauth-model.mjs.map +1 -0
  114. package/dist/internal/sql-query.d.mts +12 -0
  115. package/dist/internal/sql-query.mjs +15 -18
  116. package/dist/internal/sql-query.mjs.map +1 -1
  117. package/dist/internal/sql-storage-validation.mjs +54 -0
  118. package/dist/internal/sql-storage-validation.mjs.map +1 -0
  119. package/dist/internal/storage-mapping.mjs +1 -37
  120. package/dist/internal/storage-mapping.mjs.map +1 -1
  121. package/dist/internal/storage-plans.d.mts +7 -0
  122. package/dist/internal/storage-plans.mjs +189 -0
  123. package/dist/internal/storage-plans.mjs.map +1 -0
  124. package/dist/internal/storage-tables.d.mts +0 -56
  125. package/dist/internal/storage-tables.mjs +0 -17
  126. package/dist/internal/storage-tables.mjs.map +1 -1
  127. package/dist/internal/storage-validation.d.mts +6 -1
  128. package/dist/internal/storage-validation.mjs +42 -1
  129. package/dist/internal/storage-validation.mjs.map +1 -1
  130. package/dist/internal/transaction-kernel.mjs +3 -2
  131. package/dist/internal/transaction-kernel.mjs.map +1 -1
  132. package/package.json +1 -1
  133. package/src/Adapter.ts +89 -5
  134. package/src/OAuthPersistence.ts +427 -0
  135. package/src/index.ts +3 -0
  136. package/src/internal/configuration.ts +1 -3
  137. package/src/internal/email-kernel.ts +2 -1
  138. package/src/internal/models/oauth-connected-model.ts +396 -0
  139. package/src/internal/models/oauth-model.ts +708 -0
  140. package/src/internal/models/password-model.ts +2 -57
  141. package/src/internal/native-sql-table.ts +14 -0
  142. package/src/internal/oauth/accounts.ts +839 -0
  143. package/src/internal/oauth/connected-access.ts +388 -0
  144. package/src/internal/oauth/connected-collection.ts +345 -0
  145. package/src/internal/oauth/connected-custody.ts +308 -0
  146. package/src/internal/oauth/connected-flow.ts +562 -0
  147. package/src/internal/oauth/connected-input.ts +147 -0
  148. package/src/internal/oauth/connected-keys.ts +31 -0
  149. package/src/internal/oauth/connected-maintenance.ts +379 -0
  150. package/src/internal/oauth/connected-management.ts +391 -0
  151. package/src/internal/oauth/connected-reference.ts +135 -0
  152. package/src/internal/oauth/connected-settlement.ts +278 -0
  153. package/src/internal/oauth/connected-sign-in.ts +472 -0
  154. package/src/internal/oauth/connected-state.ts +924 -0
  155. package/src/internal/oauth/connected-target.ts +307 -0
  156. package/src/internal/oauth/flow.ts +505 -0
  157. package/src/internal/oauth/input.ts +201 -0
  158. package/src/internal/oauth/owner.ts +22 -0
  159. package/src/internal/oauth/query-compiler.ts +15 -0
  160. package/src/internal/oauth/registration.ts +676 -0
  161. package/src/internal/oauth/state.ts +218 -0
  162. package/src/internal/oauth/target.ts +803 -0
  163. package/src/internal/oauth-kernel.ts +294 -0
  164. package/src/internal/oauth-proxy.ts +176 -0
  165. package/src/internal/passkey/write-state.ts +18 -4
  166. package/src/internal/password-credentials.ts +245 -0
  167. package/src/internal/password-kernel.ts +7 -99
  168. package/src/internal/persistence.ts +1 -1
  169. package/src/internal/query-operations.ts +2 -0
  170. package/src/internal/session-kernel.ts +143 -55
  171. package/src/internal/sql-oauth-connected-model.ts +145 -0
  172. package/src/internal/sql-oauth-model.ts +263 -0
  173. package/src/internal/sql-query.ts +16 -13
  174. package/src/internal/sql-storage-validation.ts +64 -0
  175. package/src/internal/storage-mapping.ts +0 -37
  176. package/src/internal/storage-plans.ts +218 -0
  177. package/src/internal/storage-tables.ts +0 -20
  178. package/src/internal/storage-validation.ts +58 -0
  179. package/src/internal/transaction-kernel.ts +6 -2
  180. package/dist/internal/password-attempts.mjs +0 -197
  181. package/dist/internal/password-attempts.mjs.map +0 -1
  182. package/src/internal/password-attempts.ts +0 -466
@@ -8,6 +8,7 @@ import { reportPersistenceFailure } from "@yielded/auth/Persistence";
8
8
  import type { SubjectId, TokenDigest } from "@yielded/auth/Schema";
9
9
  import {
10
10
  assessAuthentication,
11
+ AuthenticationCapture,
11
12
  snapshotSessionAuthenticationProvenance,
12
13
  PendingAuthenticationInvalid,
13
14
  SessionConflict,
@@ -64,8 +65,22 @@ import {
64
65
  validateStepUpPlan,
65
66
  stepUpRotationMatches,
66
67
  } from "./step-up-state";
68
+ import { canJoinTextColumns } from "./storage-validation";
67
69
  import { NativeDatabase } from "./transaction-kernel";
68
70
 
71
+ const decodeRevocationFlag = Schema.decodeUnknownEffect(Schema.Literals([0, 1]));
72
+
73
+ /** Reads need no transaction of their own. A caller-owned transaction still
74
+ * needs a savepoint so catching an SQL failure does not leave it aborted. */
75
+ const withVerificationSavepoint = Effect.fnUntraced(function* <A, E, R>(
76
+ effect: Effect.Effect<A, E, R>,
77
+ ): Effect.fn.Return<A, E | SqlError.SqlError, R | NativeDatabase> {
78
+ const { $client } = yield* NativeDatabase;
79
+ const transaction = yield* Effect.serviceOption($client.transactionService);
80
+
81
+ return yield* Option.isSome(transaction) ? $client.withTransaction(effect) : effect;
82
+ });
83
+
69
84
  type CommitMode = "interactive" | "synchronous";
70
85
 
71
86
  type AnySubjectMapping = SessionSubjectTables<any, any>;
@@ -664,7 +679,16 @@ export const makeSessionKernel = (
664
679
  const service = {
665
680
  capture: (subjectId: SubjectId, credentialIds: ReadonlyArray<string>) =>
666
681
  safeTransaction(
667
- readSnapshot(subjectId, credentialIds).pipe(Effect.map((value) => value.revision)),
682
+ readSnapshot(subjectId, credentialIds).pipe(
683
+ Effect.flatMap((value) =>
684
+ Effect.flatMap(mapping.subject.decodeRequirement(value.subject), (requirement) =>
685
+ Schema.decodeEffect(AuthenticationCapture)({
686
+ revision: value.revision,
687
+ requirement,
688
+ }),
689
+ ),
690
+ ),
691
+ ),
668
692
  ),
669
693
  requirements: (evidence: AuthenticationEvidence) =>
670
694
  safeTransaction(
@@ -1045,6 +1069,24 @@ export const makeSessionKernel = (
1045
1069
  const readSnapshot = makeAuthoritySnapshot(tables, mapping);
1046
1070
  const c = sessionColumns(mapping);
1047
1071
 
1072
+ const verification = yield* safeTransaction(
1073
+ Effect.gen(function* () {
1074
+ const { session, subject } = yield* sqlMapping(() => ({
1075
+ session: tables(mapping.session.table).as("auth_session"),
1076
+ subject: tables(mapping.subject.table).as("auth_subject"),
1077
+ }));
1078
+
1079
+ const [sessionOwner, subjectOwner] = yield* sqlMapping(() => [
1080
+ session.unencodedTextColumn?.(mapping.session.subjectId),
1081
+ subject.unencodedTextColumn?.(mapping.subject.id),
1082
+ ]);
1083
+
1084
+ const canJoinOwner = yield* canJoinTextColumns(sessionOwner, subjectOwner);
1085
+
1086
+ return { session, subject, canJoinOwner };
1087
+ }),
1088
+ );
1089
+
1048
1090
  const persistence = {
1049
1091
  establish: <A>(
1050
1092
  input: Parameters<StatefulSessionPersistence<Claims>["establish"]>[0],
@@ -1072,14 +1114,15 @@ export const makeSessionKernel = (
1072
1114
  let flowInsert: unknown | undefined;
1073
1115
  let pendingExpiresAt: DateTime.Utc | undefined;
1074
1116
 
1075
- if (input.pending === undefined)
1076
- flowInsert = yield* ensureDirectFlowAvailable(
1077
- mapping,
1078
- input.evidence,
1079
- nativeSubjectId,
1080
- input.session.absoluteExpiresAt,
1081
- );
1082
- else {
1117
+ if (input.pending === undefined) {
1118
+ if (input.fresh !== true || input.handoffSourceSessionId !== undefined)
1119
+ flowInsert = yield* ensureDirectFlowAvailable(
1120
+ mapping,
1121
+ input.evidence,
1122
+ nativeSubjectId,
1123
+ input.session.absoluteExpiresAt,
1124
+ );
1125
+ } else {
1083
1126
  if (mapping.pending === undefined) return yield* invalidPending();
1084
1127
  const pending = yield* validatePending(mapping, input.pending);
1085
1128
 
@@ -1137,7 +1180,7 @@ export const makeSessionKernel = (
1137
1180
 
1138
1181
  if (flowInsert !== undefined)
1139
1182
  yield* transaction.insert(mapping.flow.table).values(flowInsert);
1140
- else
1183
+ else if (input.pending !== undefined)
1141
1184
  yield* consumePending(
1142
1185
  { ...mapping, pending: mapping.pending! },
1143
1186
  input.pending!,
@@ -1164,32 +1207,55 @@ export const makeSessionKernel = (
1164
1207
  return receipt;
1165
1208
  }).pipe(normalizeMutation(mapping)),
1166
1209
  ),
1167
- verify: (input: any) =>
1210
+ verify: (input: Parameters<StatefulSessionPersistence<Claims>["verify"]>[0]) =>
1168
1211
  safeTransaction(
1169
- inTransaction(
1170
- database,
1212
+ withVerificationSavepoint(
1171
1213
  Effect.gen(function* () {
1172
- const read = yield* CurrentSessionSql;
1214
+ const sql = root.$client.withoutTransforms();
1215
+ const { session, subject, canJoinOwner } = verification;
1173
1216
 
1174
- const rows = yield* read
1175
- .select()
1176
- .from(mapping.session.table)
1177
- .where(eq(c.digest, input.digest))
1178
- .limit(1);
1217
+ // A digest carries no owner. Custom codecs require decoding that
1218
+ // native ID first; never compare differently encoded ID columns.
1219
+ const owner = canJoinOwner
1220
+ ? undefined
1221
+ : (yield* database
1222
+ .select({ subjectId: c.subjectId })
1223
+ .from(mapping.session.table)
1224
+ .where(eq(c.digest, input.digest))
1225
+ .limit(1))[0];
1226
+
1227
+ if (!canJoinOwner && owner === undefined) return yield* invalidSession();
1228
+
1229
+ // The final statement reads both rows even after owner discovery,
1230
+ // so deletion, rotation or owner changes cannot reuse the first read.
1231
+ const rows = yield* sqlMapping(
1232
+ () => sql<
1233
+ Record<string, unknown>
1234
+ >`select ${session.fields("v_")}, ${subject.fields("s_")}
1235
+ from ${session.name} inner join ${subject.name} on
1236
+ ${subject.column(mapping.subject.id)} = ${
1237
+ owner === undefined
1238
+ ? session.column(mapping.session.subjectId)
1239
+ : subject.value(mapping.subject.id, owner.subjectId)
1240
+ }
1241
+ where ${session.column(mapping.session.digest)} = ${session.value(mapping.session.digest, input.digest)}
1242
+ limit 1`,
1243
+ ).pipe(Effect.flatten);
1179
1244
 
1180
1245
  const row = rows[0];
1181
1246
 
1182
1247
  if (row === undefined) return yield* invalidSession();
1183
- const record = yield* mapping.session.decode(row);
1248
+ const sessionRow = yield* sqlMapping(() => session.decode(row, "v_"));
1249
+ const subjectRow = yield* sqlMapping(() => subject.decode(row, "s_"));
1250
+ const record = yield* mapping.session.decode(sessionRow);
1184
1251
  const nativeSubjectId = yield* mapping.subjectId.toNative(record.subjectId);
1185
- const subjects = yield* readSubject(mapping, nativeSubjectId, false);
1186
- const subject = subjects[0];
1187
1252
  const now = yield* freshNow;
1188
1253
 
1189
1254
  if (
1190
- subject === undefined ||
1191
- !mapping.subject.isActiveStatus(subject[mapping.subject.status]) ||
1192
- subject[mapping.subject.securityRevision] !== record.securityRevision ||
1255
+ !mapping.subjectId.equals(nativeSubjectId, sessionRow[mapping.session.subjectId]) ||
1256
+ !mapping.subjectId.equals(nativeSubjectId, subjectRow[mapping.subject.id]) ||
1257
+ !mapping.subject.isActiveStatus(subjectRow[mapping.subject.status]) ||
1258
+ subjectRow[mapping.subject.securityRevision] !== record.securityRevision ||
1193
1259
  DateTime.toEpochMillis(now) >= DateTime.toEpochMillis(record.expiresAt) ||
1194
1260
  DateTime.toEpochMillis(now) >= DateTime.toEpochMillis(record.absoluteExpiresAt)
1195
1261
  )
@@ -1467,7 +1533,12 @@ export const makeSessionKernel = (
1467
1533
  Effect.provideService(LifecycleHooks, hooks),
1468
1534
  ),
1469
1535
  verify: (input) =>
1470
- persistence.verify(input).pipe(Effect.provideService(LifecycleHooks, hooks)),
1536
+ persistence
1537
+ .verify(input)
1538
+ .pipe(
1539
+ Effect.provideService(NativeDatabase, root),
1540
+ Effect.provideService(LifecycleHooks, hooks),
1541
+ ),
1471
1542
  rotate: (input, prepare) =>
1472
1543
  persistence.rotate(input, prepare).pipe(Effect.provideService(LifecycleHooks, hooks)),
1473
1544
  revokeDigest: (digest, prepare) =>
@@ -1509,50 +1580,62 @@ export const makeSessionKernel = (
1509
1580
  absoluteExpiresAt: column(mapping.tombstone.table, mapping.tombstone.absoluteExpiresAt),
1510
1581
  };
1511
1582
 
1583
+ const root = yield* NativeDatabase;
1584
+
1585
+ const tables = yield* sqlMapping(() =>
1586
+ nativeTables(root.$client.withoutTransforms(), root),
1587
+ ).pipe(Effect.mapError(unavailable));
1588
+
1512
1589
  const service = {
1513
1590
  verify: (session: SessionMetadata, _now: DateTime.Utc) =>
1514
1591
  safeTransaction(
1515
- inTransaction(
1516
- database,
1592
+ withVerificationSavepoint(
1517
1593
  Effect.gen(function* () {
1518
- const read = yield* CurrentSessionSql;
1594
+ const sql = root.$client.withoutTransforms();
1519
1595
  const nativeSubjectId = yield* mapping.subjectId.toNative(session.subjectId);
1520
1596
  const nativeSessionId = yield* mapping.sessionId.toNative(session.sessionId);
1521
- const subjects = yield* readSubject(mapping, nativeSubjectId, false);
1522
- const row = subjects[0];
1597
+ const readNow = yield* freshNow;
1598
+
1599
+ // Independent bound IDs preserve each column's physical type and
1600
+ // codec. Both authority and revocation come from this statement.
1601
+ const { subjectTable, rows } = yield* sqlMapping(() => {
1602
+ const subjectTable = tables(mapping.subject.table).as("auth_subject");
1603
+ const revoked = tables(mapping.tombstone.table).as("auth_revocation");
1604
+
1605
+ return {
1606
+ subjectTable,
1607
+ rows: sql<Record<string, unknown>>`
1608
+ select ${subjectTable.fields("s_")},
1609
+ case when exists (
1610
+ select 1 from ${revoked.name}
1611
+ where ${revoked.column(mapping.tombstone.subjectId)} = ${revoked.value(mapping.tombstone.subjectId, nativeSubjectId)}
1612
+ and ${revoked.column(mapping.tombstone.sessionId)} = ${revoked.value(mapping.tombstone.sessionId, nativeSessionId)}
1613
+ and ${revoked.column(mapping.tombstone.absoluteExpiresAt)} > ${revoked.value(mapping.tombstone.absoluteExpiresAt, mapping.tombstone.encodeInstant(readNow))}
1614
+ ) then 1 else 0 end as auth_revoked
1615
+ from ${subjectTable.name}
1616
+ where ${subjectTable.column(mapping.subject.id)} = ${subjectTable.value(mapping.subject.id, nativeSubjectId)}
1617
+ limit 1`,
1618
+ };
1619
+ });
1523
1620
 
1524
- if (
1525
- row === undefined ||
1526
- !mapping.subject.isActiveStatus(row[mapping.subject.status]) ||
1527
- row[mapping.subject.securityRevision] !== session.securityRevision
1528
- )
1529
- return yield* invalidSession();
1621
+ const result = (yield* rows)[0];
1622
+
1623
+ if (result === undefined) return yield* invalidSession();
1624
+ const row = yield* sqlMapping(() => subjectTable.decode(result, "s_"));
1625
+ const revoked = yield* decodeRevocationFlag(result.auth_revoked);
1530
1626
  const authoritativeNow = yield* freshNow;
1531
1627
 
1532
1628
  if (
1629
+ !mapping.subjectId.equals(nativeSubjectId, row[mapping.subject.id]) ||
1630
+ !mapping.subject.isActiveStatus(row[mapping.subject.status]) ||
1631
+ row[mapping.subject.securityRevision] !== session.securityRevision ||
1632
+ revoked === 1 ||
1533
1633
  DateTime.toEpochMillis(authoritativeNow) >=
1534
1634
  DateTime.toEpochMillis(session.expiresAt) ||
1535
1635
  DateTime.toEpochMillis(authoritativeNow) >=
1536
1636
  DateTime.toEpochMillis(session.absoluteExpiresAt)
1537
1637
  )
1538
1638
  return yield* invalidSession();
1539
-
1540
- const revoked = yield* read
1541
- .select({ sessionId: tombstone.sessionId })
1542
- .from(mapping.tombstone.table)
1543
- .where(
1544
- and(
1545
- eq(tombstone.subjectId, nativeSubjectId),
1546
- eq(tombstone.sessionId, nativeSessionId),
1547
- gt(
1548
- tombstone.absoluteExpiresAt,
1549
- mapping.tombstone.encodeInstant(authoritativeNow),
1550
- ),
1551
- ),
1552
- )
1553
- .limit(1);
1554
-
1555
- if (revoked.length > 0) return yield* invalidSession();
1556
1639
  }),
1557
1640
  ),
1558
1641
  ),
@@ -1678,7 +1761,12 @@ export const makeSessionKernel = (
1678
1761
 
1679
1762
  return {
1680
1763
  verify: (session, now) =>
1681
- service.verify(session, now).pipe(Effect.provideService(LifecycleHooks, hooks)),
1764
+ service
1765
+ .verify(session, now)
1766
+ .pipe(
1767
+ Effect.provideService(NativeDatabase, root),
1768
+ Effect.provideService(LifecycleHooks, hooks),
1769
+ ),
1682
1770
  revoke: (input, prepare) =>
1683
1771
  service.revoke(input, prepare).pipe(Effect.provideService(LifecycleHooks, hooks)),
1684
1772
  revokeAll: (input, prepare) =>
@@ -0,0 +1,145 @@
1
+ import * as Shared from "./models/oauth-connected-model";
2
+ import type { SqlTableModel } from "./sql-oauth-model";
3
+ import type { Fragment as SQL, Table } from "./sql-table";
4
+
5
+ export {
6
+ type OAuthConnectedAction,
7
+ type OAuthConnectedOrderCodec,
8
+ type OAuthConnectedPolicyInput,
9
+ requiredOAuthConnectedConstraints,
10
+ requiredOAuthConnectedRevocationConstraints,
11
+ } from "./models/oauth-connected-model";
12
+
13
+ export type OAuthConnectedSubjectTable<S extends Table> = Shared.OAuthConnectedSubjectTable<
14
+ SqlTableModel<S>,
15
+ SQL
16
+ >;
17
+
18
+ export type OAuthConnectedFlowTable<F extends Table, N> = Shared.OAuthConnectedFlowTable<
19
+ SqlTableModel<F>,
20
+ N
21
+ >;
22
+
23
+ export type OAuthConnectedGrantTable<G extends Table, N> = Shared.OAuthConnectedGrantTable<
24
+ SqlTableModel<G>,
25
+ N
26
+ >;
27
+
28
+ /** Also stores a permanent provider/issuer scope anchor. clientKey must fit 52
29
+ * ASCII characters. Empty clientRegistrationId is reserved for that anchor;
30
+ * actual profile registrations are nonempty. Scope counter stays zero. */
31
+ export type OAuthConnectedClientRegistrationTable<C extends Table> =
32
+ Shared.OAuthConnectedClientRegistrationTable<SqlTableModel<C>>;
33
+
34
+ /** Remote authority: no local-subject column or subject-scoped key. Retain this
35
+ * anchor/cutoff when a legitimately released tuple gains a different local owner. */
36
+ export type OAuthConnectedCohortTable<C extends Table> = Shared.OAuthConnectedCohortTable<
37
+ SqlTableModel<C>
38
+ >;
39
+
40
+ export type OAuthConnectedAdmissionTable<A extends Table, N> = Shared.OAuthConnectedAdmissionTable<
41
+ SqlTableModel<A>,
42
+ N
43
+ >;
44
+
45
+ export type OAuthConnectedCommandTable<C extends Table, N> = Shared.OAuthConnectedCommandTable<
46
+ SqlTableModel<C>,
47
+ N
48
+ >;
49
+
50
+ export type OAuthConnectedRevocationJobTable<
51
+ J extends Table,
52
+ N,
53
+ > = Shared.OAuthConnectedRevocationJobTable<SqlTableModel<J>, N>;
54
+
55
+ export type OAuthConnectedPolicyGuardTable<
56
+ T extends Table,
57
+ N,
58
+ > = Shared.OAuthConnectedPolicyGuardTable<SqlTableModel<T>, N, SQL>;
59
+
60
+ export type OAuthConnectedPolicyGuard<N> = Shared.OAuthConnectedPolicyGuard<N, SQL, Table>;
61
+
62
+ export const oauthConnectedPolicyGuard = <T extends Table, N>(
63
+ input: OAuthConnectedPolicyGuardTable<T, N>,
64
+ ): OAuthConnectedPolicyGuard<N> =>
65
+ Shared.oauthConnectedPolicyGuard<SqlTableModel<T>, N, SQL>(input);
66
+
67
+ export type OAuthConnectedSqlPolicy<N> = Shared.OAuthConnectedSqlPolicy<N, SQL, Table>;
68
+
69
+ /** Existing ownership only; workers never allocate or acquire a tuple. */
70
+ export type OAuthConnectedOwnershipRead<
71
+ T extends Table,
72
+ O extends Table,
73
+ N,
74
+ > = Shared.OAuthConnectedOwnershipRead<SqlTableModel<T>, SqlTableModel<O>, N, SQL>;
75
+
76
+ export type OAuthConnectedAuthorityMapping<
77
+ T extends Table,
78
+ O extends Table,
79
+ F extends Table,
80
+ G extends Table,
81
+ C extends Table,
82
+ H extends Table,
83
+ N,
84
+ > = Shared.OAuthConnectedAuthorityMapping<
85
+ SqlTableModel<T>,
86
+ SqlTableModel<O>,
87
+ SqlTableModel<F>,
88
+ SqlTableModel<G>,
89
+ SqlTableModel<C>,
90
+ SqlTableModel<H>,
91
+ N,
92
+ SQL
93
+ >;
94
+
95
+ export type OAuthConnectedMapping<
96
+ S extends Table,
97
+ AC extends Table,
98
+ T extends Table,
99
+ O extends Table,
100
+ F extends Table,
101
+ G extends Table,
102
+ C extends Table,
103
+ H extends Table,
104
+ A extends Table,
105
+ D extends Table,
106
+ N,
107
+ J extends Table = never,
108
+ > = Shared.OAuthConnectedMapping<
109
+ SqlTableModel<S>,
110
+ SqlTableModel<AC>,
111
+ SqlTableModel<T>,
112
+ SqlTableModel<O>,
113
+ SqlTableModel<F>,
114
+ SqlTableModel<G>,
115
+ SqlTableModel<C>,
116
+ SqlTableModel<H>,
117
+ SqlTableModel<A>,
118
+ SqlTableModel<D>,
119
+ N,
120
+ SqlTableModel<J>,
121
+ SQL,
122
+ Table,
123
+ SqlTableModel<Table>
124
+ >;
125
+
126
+ export type OAuthConnectedRevocationMapping<
127
+ T extends Table,
128
+ O extends Table,
129
+ F extends Table,
130
+ G extends Table,
131
+ C extends Table,
132
+ H extends Table,
133
+ J extends Table,
134
+ N,
135
+ > = Shared.OAuthConnectedRevocationMapping<
136
+ SqlTableModel<T>,
137
+ SqlTableModel<O>,
138
+ SqlTableModel<F>,
139
+ SqlTableModel<G>,
140
+ SqlTableModel<C>,
141
+ SqlTableModel<H>,
142
+ SqlTableModel<J>,
143
+ N,
144
+ SQL
145
+ >;