@effect-auth/core 0.1.0-alpha.18 → 0.1.0-alpha.19

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 (118) hide show
  1. package/README.md +4 -5
  2. package/dist/DevEmail.d.ts +44 -0
  3. package/dist/DevEmail.d.ts.map +1 -0
  4. package/dist/DevEmail.js +115 -0
  5. package/dist/DevEmail.js.map +1 -0
  6. package/dist/DevelopmentSeed.d.ts +47 -0
  7. package/dist/DevelopmentSeed.d.ts.map +1 -0
  8. package/dist/DevelopmentSeed.js +337 -0
  9. package/dist/DevelopmentSeed.js.map +1 -0
  10. package/dist/DrizzleBunSqliteStorage.d.ts +1 -1
  11. package/dist/DrizzleBunSqliteStorage.d.ts.map +1 -1
  12. package/dist/DrizzleD1SqliteStorage.d.ts +1 -1
  13. package/dist/DrizzleD1SqliteStorage.d.ts.map +1 -1
  14. package/dist/DrizzleEffectSqliteStorage.d.ts +1 -1
  15. package/dist/DrizzleEffectSqliteStorage.d.ts.map +1 -1
  16. package/dist/DrizzleNodeSqliteStorage.d.ts +1 -1
  17. package/dist/DrizzleNodeSqliteStorage.d.ts.map +1 -1
  18. package/dist/DrizzlePostgresStorage.d.ts +26 -0
  19. package/dist/DrizzlePostgresStorage.d.ts.map +1 -0
  20. package/dist/DrizzlePostgresStorage.js +14 -0
  21. package/dist/DrizzlePostgresStorage.js.map +1 -0
  22. package/dist/EffectQbPostgresStorage.d.ts +8 -1
  23. package/dist/EffectQbPostgresStorage.d.ts.map +1 -1
  24. package/dist/EffectQbPostgresStorage.js +359 -2
  25. package/dist/EffectQbPostgresStorage.js.map +1 -1
  26. package/dist/EffectQbSqliteStorage.d.ts +42 -2
  27. package/dist/EffectQbSqliteStorage.d.ts.map +1 -1
  28. package/dist/EffectQbSqliteStorage.js +337 -1
  29. package/dist/EffectQbSqliteStorage.js.map +1 -1
  30. package/dist/HttpApi/Api.d.ts +151 -98
  31. package/dist/HttpApi/Api.d.ts.map +1 -1
  32. package/dist/HttpApi/Api.js +122 -72
  33. package/dist/HttpApi/Api.js.map +1 -1
  34. package/dist/HttpApi/ClientApi.d.ts +136 -136
  35. package/dist/HttpApi/Endpoints.d.ts +33 -1
  36. package/dist/HttpApi/Endpoints.d.ts.map +1 -1
  37. package/dist/HttpApi/Endpoints.js +31 -2
  38. package/dist/HttpApi/Endpoints.js.map +1 -1
  39. package/dist/HttpApi/Errors.d.ts +11 -3
  40. package/dist/HttpApi/Errors.d.ts.map +1 -1
  41. package/dist/HttpApi/Errors.js +16 -0
  42. package/dist/HttpApi/Errors.js.map +1 -1
  43. package/dist/HttpApi/Middleware.d.ts +12 -0
  44. package/dist/HttpApi/Middleware.d.ts.map +1 -1
  45. package/dist/HttpApi/Middleware.js +8 -0
  46. package/dist/HttpApi/Middleware.js.map +1 -1
  47. package/dist/HttpApi/OAuthDeviceAuthorization.d.ts +106 -0
  48. package/dist/HttpApi/OAuthDeviceAuthorization.d.ts.map +1 -0
  49. package/dist/HttpApi/OAuthDeviceAuthorization.js +415 -0
  50. package/dist/HttpApi/OAuthDeviceAuthorization.js.map +1 -0
  51. package/dist/HttpApi/OAuthToken.d.ts +1 -1
  52. package/dist/HttpApi/OAuthToken.d.ts.map +1 -1
  53. package/dist/HttpApi/OAuthToken.js +1 -1
  54. package/dist/HttpApi/OAuthToken.js.map +1 -1
  55. package/dist/HttpApi/Operations/OAuthDeviceAuthorization.d.ts +23 -0
  56. package/dist/HttpApi/Operations/OAuthDeviceAuthorization.d.ts.map +1 -0
  57. package/dist/HttpApi/Operations/OAuthDeviceAuthorization.js +7 -0
  58. package/dist/HttpApi/Operations/OAuthDeviceAuthorization.js.map +1 -0
  59. package/dist/HttpApi/Operations/OAuthToken.d.ts +10 -0
  60. package/dist/HttpApi/Operations/OAuthToken.d.ts.map +1 -1
  61. package/dist/HttpApi/Operations/OAuthToken.js +4 -0
  62. package/dist/HttpApi/Operations/OAuthToken.js.map +1 -1
  63. package/dist/HttpApi/Operations/index.d.ts +1 -0
  64. package/dist/HttpApi/Operations/index.d.ts.map +1 -1
  65. package/dist/HttpApi/Operations/index.js +1 -0
  66. package/dist/HttpApi/Operations/index.js.map +1 -1
  67. package/dist/HttpApi/PasswordContract.d.ts +9 -9
  68. package/dist/HttpApi/Schemas.d.ts +38 -0
  69. package/dist/HttpApi/Schemas.d.ts.map +1 -1
  70. package/dist/HttpApi/Schemas.js +36 -1
  71. package/dist/HttpApi/Schemas.js.map +1 -1
  72. package/dist/HttpApi/index.d.ts +3 -1
  73. package/dist/HttpApi/index.d.ts.map +1 -1
  74. package/dist/HttpApi/index.js +3 -1
  75. package/dist/HttpApi/index.js.map +1 -1
  76. package/dist/OAuth.d.ts +46 -1
  77. package/dist/OAuth.d.ts.map +1 -1
  78. package/dist/OAuth.js +118 -4
  79. package/dist/OAuth.js.map +1 -1
  80. package/dist/OAuthDeviceAuthorization.d.ts +378 -0
  81. package/dist/OAuthDeviceAuthorization.d.ts.map +1 -0
  82. package/dist/OAuthDeviceAuthorization.js +860 -0
  83. package/dist/OAuthDeviceAuthorization.js.map +1 -0
  84. package/dist/OAuthDeviceClient.d.ts +76 -0
  85. package/dist/OAuthDeviceClient.d.ts.map +1 -0
  86. package/dist/OAuthDeviceClient.js +362 -0
  87. package/dist/OAuthDeviceClient.js.map +1 -0
  88. package/dist/PostgresStorageMigrations.d.ts +2 -1
  89. package/dist/PostgresStorageMigrations.d.ts.map +1 -1
  90. package/dist/PostgresStorageMigrations.js +54 -0
  91. package/dist/PostgresStorageMigrations.js.map +1 -1
  92. package/dist/Retention.d.ts +6 -1
  93. package/dist/Retention.d.ts.map +1 -1
  94. package/dist/Retention.js +49 -12
  95. package/dist/Retention.js.map +1 -1
  96. package/dist/SqliteStorageSchemaData.d.ts +2 -0
  97. package/dist/SqliteStorageSchemaData.d.ts.map +1 -0
  98. package/dist/SqliteStorageSchemaData.js +508 -0
  99. package/dist/SqliteStorageSchemaData.js.map +1 -0
  100. package/dist/StorageMigrations.d.ts +2 -1
  101. package/dist/StorageMigrations.d.ts.map +1 -1
  102. package/dist/StorageMigrations.js +32 -0
  103. package/dist/StorageMigrations.js.map +1 -1
  104. package/dist/StorageSchemaGenerator.d.ts +70 -12
  105. package/dist/StorageSchemaGenerator.d.ts.map +1 -1
  106. package/dist/StorageSchemaGenerator.js +478 -82
  107. package/dist/StorageSchemaGenerator.js.map +1 -1
  108. package/migrations/0029_auth_oauth_device_authorization.sql +28 -0
  109. package/migrations/postgres/0010_auth_oauth_device_authorization.sql +50 -0
  110. package/package.json +24 -16
  111. package/dist/DrizzleDurableObjectSqliteStorage.d.ts +0 -19
  112. package/dist/DrizzleDurableObjectSqliteStorage.d.ts.map +0 -1
  113. package/dist/DrizzleDurableObjectSqliteStorage.js +0 -14
  114. package/dist/DrizzleDurableObjectSqliteStorage.js.map +0 -1
  115. package/dist/DrizzleWasmSqliteStorage.d.ts +0 -23
  116. package/dist/DrizzleWasmSqliteStorage.d.ts.map +0 -1
  117. package/dist/DrizzleWasmSqliteStorage.js +0 -15
  118. package/dist/DrizzleWasmSqliteStorage.js.map +0 -1
@@ -1,4 +1,4 @@
1
- // oxlint-disable complexity, typescript/no-explicit-any
1
+ // oxlint-disable complexity, promise/prefer-await-to-then, typescript/no-explicit-any
2
2
  import { Cause, Data, Effect, Layer, Option, Redacted, Schema } from "effect";
3
3
  import { Column, Executor as CoreExecutor, Query, RowSet, Scalar, Table, } from "effect-qb";
4
4
  import { Column as PostgresColumn, Errors as PostgresErrors, Executor, Jsonb, Query as PostgresQuery, Renderer, } from "effect-qb/postgres";
@@ -14,6 +14,7 @@ import { JwtError, JwtRevocationStore } from "./Jwt.js";
14
14
  import { LoginApprovalReviewError, LoginApprovalReviewStore, } from "./LoginApproval.js";
15
15
  import { LoginRiskContext, LoginRiskHistory, LoginRiskHistoryError, LoginRiskSignal, maxLoginRiskLevel, } from "./LoginRisk.js";
16
16
  import { OAuthAuthorizationCodeStore, OAuthAuthorizationCodeStoreError, OAuthAccountStore, OAuthAccountStoreError, OAuthClientSecretHash, OAuthClientSecretPrefix, OAuthClientSecretStore, OAuthClientSecretStoreError, OAuthClientStore, OAuthClientStoreError, OAuthCodeChallenge, OAuthConsentStore, OAuthConsentStoreError, OAuthIdentityBridgeConflictError, OAuthIdentityBridgeStore, OAuthIdentityBridgeStoreError, OAuthProviderModeTokenStore, OAuthProviderModeTokenStoreError, OAuthProviderTokenVault, OAuthProviderTokenVaultError, } from "./OAuth.js";
17
+ import { OAuthDeviceAuthorizationId, OAuthDeviceAuthorizationStore, OAuthDeviceAuthorizationStoreError, OAuthDeviceCodeTokenCommitStore, OAuthDeviceCodeTokenCommitStoreError, } from "./OAuthDeviceAuthorization.js";
17
18
  import { PasskeyCredentialId, PasskeyCredentialStore, PasskeyCredentialStoreError, } from "./Passkey.js";
18
19
  import { canonicalPermissionScope, normalizePermissionDefinitionListLimit, PermissionStore, PermissionStoreError, } from "./Permission.js";
19
20
  import { RecoveryCodeHash, RecoveryCodeLoginCommitStore, RecoveryCodeLoginCommitStoreError, RecoveryCodeSessionRotateStore, RecoveryCodeSessionRotateStoreError, RecoveryCodeStore, RecoveryCodeStoreError, } from "./RecoveryCode.js";
@@ -210,6 +211,25 @@ const authPostgresOAuthAuthorizationCode = Table.make("auth_oauth_authorization_
210
211
  consumed_at: nullableInt8(),
211
212
  metadata: jsonb(),
212
213
  });
214
+ const authPostgresOAuthDeviceAuthorization = Table.make("auth_oauth_device_authorization", {
215
+ id: Column.text().pipe(Column.primaryKey),
216
+ device_code_hash: Column.text().pipe(Column.unique),
217
+ user_code_hash: Column.text().pipe(Column.unique),
218
+ client_id: Column.text(),
219
+ requested_scopes: PostgresColumn.jsonb(Schema.Unknown),
220
+ granted_scopes: jsonb(),
221
+ subject: Column.text().pipe(Column.nullable),
222
+ status: Column.text(),
223
+ issued_at: int8(),
224
+ expires_at: int8(),
225
+ poll_interval_seconds: Column.int(),
226
+ next_poll_at: int8(),
227
+ last_polled_at: nullableInt8(),
228
+ approved_at: nullableInt8(),
229
+ denied_at: nullableInt8(),
230
+ consumed_at: nullableInt8(),
231
+ metadata: jsonb(),
232
+ });
213
233
  const authPostgresOAuthClientSecret = Table.make("auth_oauth_client_secret", {
214
234
  prefix: Column.text(),
215
235
  client_id: Column.text(),
@@ -415,6 +435,7 @@ export const effectQbPostgresTables = {
415
435
  oauthClient: authPostgresOAuthClient,
416
436
  oauthConsent: authPostgresOAuthConsent,
417
437
  oauthAuthorizationCode: authPostgresOAuthAuthorizationCode,
438
+ oauthDeviceAuthorization: authPostgresOAuthDeviceAuthorization,
418
439
  oauthClientSecret: authPostgresOAuthClientSecret,
419
440
  oauthProviderModeToken: authPostgresOAuthProviderModeToken,
420
441
  oauthProviderTokenVault: authPostgresOAuthProviderTokenVault,
@@ -607,6 +628,25 @@ const oauthAuthorizationCodeSelection = {
607
628
  consumedAt: authPostgresOAuthAuthorizationCode.consumed_at,
608
629
  metadata: authPostgresOAuthAuthorizationCode.metadata,
609
630
  };
631
+ const oauthDeviceAuthorizationSelection = {
632
+ id: authPostgresOAuthDeviceAuthorization.id,
633
+ deviceCodeHash: authPostgresOAuthDeviceAuthorization.device_code_hash,
634
+ userCodeHash: authPostgresOAuthDeviceAuthorization.user_code_hash,
635
+ clientId: authPostgresOAuthDeviceAuthorization.client_id,
636
+ requestedScopes: authPostgresOAuthDeviceAuthorization.requested_scopes,
637
+ grantedScopes: authPostgresOAuthDeviceAuthorization.granted_scopes,
638
+ subject: authPostgresOAuthDeviceAuthorization.subject,
639
+ status: authPostgresOAuthDeviceAuthorization.status,
640
+ issuedAt: authPostgresOAuthDeviceAuthorization.issued_at,
641
+ expiresAt: authPostgresOAuthDeviceAuthorization.expires_at,
642
+ pollIntervalSeconds: authPostgresOAuthDeviceAuthorization.poll_interval_seconds,
643
+ nextPollAt: authPostgresOAuthDeviceAuthorization.next_poll_at,
644
+ lastPolledAt: authPostgresOAuthDeviceAuthorization.last_polled_at,
645
+ approvedAt: authPostgresOAuthDeviceAuthorization.approved_at,
646
+ deniedAt: authPostgresOAuthDeviceAuthorization.denied_at,
647
+ consumedAt: authPostgresOAuthDeviceAuthorization.consumed_at,
648
+ metadata: authPostgresOAuthDeviceAuthorization.metadata,
649
+ };
610
650
  const oauthClientSecretSelection = {
611
651
  prefix: authPostgresOAuthClientSecret.prefix,
612
652
  clientId: authPostgresOAuthClientSecret.client_id,
@@ -1155,6 +1195,48 @@ const decodeOAuthAuthorizationCode = (row) => ({
1155
1195
  ? {}
1156
1196
  : { metadata: optionalRecord(row.metadata) }),
1157
1197
  });
1198
+ const decodeOAuthDeviceAuthorization = (row) => {
1199
+ const base = {
1200
+ id: OAuthDeviceAuthorizationId(String(row.id)),
1201
+ deviceCodeHash: String(row.deviceCodeHash ?? row.device_code_hash),
1202
+ userCodeHash: String(row.userCodeHash ?? row.user_code_hash),
1203
+ clientId: OAuthClientId(String(row.clientId ?? row.client_id)),
1204
+ requestedScopes: stringArray(row.requestedScopes ?? row.requested_scopes),
1205
+ issuedAt: postgresBigintToUnixMillis(row.issuedAt ?? row.issued_at),
1206
+ expiresAt: postgresBigintToUnixMillis(row.expiresAt ?? row.expires_at),
1207
+ pollIntervalSeconds: Number(row.pollIntervalSeconds ?? row.poll_interval_seconds),
1208
+ nextPollAt: postgresBigintToUnixMillis(row.nextPollAt ?? row.next_poll_at),
1209
+ ...(optionalMillis(row.lastPolledAt ?? row.last_polled_at) === undefined
1210
+ ? {}
1211
+ : {
1212
+ lastPolledAt: optionalMillis(row.lastPolledAt ?? row.last_polled_at),
1213
+ }),
1214
+ ...(optionalRecord(row.metadata) === undefined
1215
+ ? {}
1216
+ : { metadata: optionalRecord(row.metadata) }),
1217
+ };
1218
+ const status = String(row.status);
1219
+ if (status === "approved") {
1220
+ return {
1221
+ ...base,
1222
+ status: "approved",
1223
+ subject: String(row.subject),
1224
+ grantedScopes: stringArray(row.grantedScopes ?? row.granted_scopes),
1225
+ approvedAt: postgresBigintToUnixMillis(row.approvedAt ?? row.approved_at),
1226
+ ...(optionalMillis(row.consumedAt ?? row.consumed_at) === undefined
1227
+ ? {}
1228
+ : { consumedAt: optionalMillis(row.consumedAt ?? row.consumed_at) }),
1229
+ };
1230
+ }
1231
+ if (status === "denied") {
1232
+ return {
1233
+ ...base,
1234
+ status: "denied",
1235
+ deniedAt: postgresBigintToUnixMillis(row.deniedAt ?? row.denied_at),
1236
+ };
1237
+ }
1238
+ return { ...base, status: "pending" };
1239
+ };
1158
1240
  const decodeOAuthClientSecret = (row) => ({
1159
1241
  prefix: OAuthClientSecretPrefix(String(row.prefix)),
1160
1242
  clientId: OAuthClientId(String(row.clientId ?? row.client_id)),
@@ -1726,6 +1808,25 @@ const oauthAuthorizationCodeValues = (row) => ({
1726
1808
  consumed_at: row.consumedAt === undefined ? null : millisParam(row.consumedAt),
1727
1809
  metadata: row.metadata ?? null,
1728
1810
  });
1811
+ const oauthDeviceAuthorizationValues = (row) => ({
1812
+ id: row.id,
1813
+ device_code_hash: row.deviceCodeHash,
1814
+ user_code_hash: row.userCodeHash,
1815
+ client_id: row.clientId,
1816
+ requested_scopes: row.requestedScopes,
1817
+ granted_scopes: null,
1818
+ subject: null,
1819
+ status: "pending",
1820
+ issued_at: millisParam(row.issuedAt),
1821
+ expires_at: millisParam(row.expiresAt),
1822
+ poll_interval_seconds: row.pollIntervalSeconds,
1823
+ next_poll_at: millisParam(row.nextPollAt),
1824
+ last_polled_at: row.lastPolledAt === undefined ? null : millisParam(row.lastPolledAt),
1825
+ approved_at: null,
1826
+ denied_at: null,
1827
+ consumed_at: null,
1828
+ metadata: row.metadata ?? null,
1829
+ });
1729
1830
  const oauthClientSecretValues = (row) => ({
1730
1831
  prefix: row.prefix,
1731
1832
  client_id: row.clientId,
@@ -2098,6 +2199,16 @@ const oauthAuthorizationCodeStoreError = (operation, cause) => new OAuthAuthoriz
2098
2199
  message: `OAuth authorization code ${operation} failed`,
2099
2200
  cause,
2100
2201
  });
2202
+ const oauthDeviceAuthorizationStoreError = (operation, cause) => new OAuthDeviceAuthorizationStoreError({
2203
+ operation,
2204
+ message: `OAuth device authorization ${operation} failed`,
2205
+ cause,
2206
+ });
2207
+ const oauthDeviceCodeTokenCommitStoreError = (cause) => new OAuthDeviceCodeTokenCommitStoreError({
2208
+ operation: "commit",
2209
+ message: "OAuth device token commit failed",
2210
+ cause,
2211
+ });
2101
2212
  const oauthClientSecretStoreError = (operation, cause) => new OAuthClientSecretStoreError({
2102
2213
  operation,
2103
2214
  message: `OAuth client secret ${operation} failed`,
@@ -3694,6 +3805,250 @@ export const makeEffectQbPostgresOAuthAuthorizationCodeStore = (executor, sql) =
3694
3805
  },
3695
3806
  });
3696
3807
  };
3808
+ export const makeEffectQbPostgresOAuthDeviceAuthorizationStore = (executor, sql) => {
3809
+ const find = (column, value, operation) => {
3810
+ const mapError = (cause) => oauthDeviceAuthorizationStoreError(operation, cause);
3811
+ return executeMapped(executor, () => Query.select(oauthDeviceAuthorizationSelection).pipe(Query.from(authPostgresOAuthDeviceAuthorization), Query.where(eq(column, value))), mapError).pipe(Effect.flatMap((rows) => decodeMapped(() => firstOption(rows, decodeOAuthDeviceAuthorization), mapError)));
3812
+ };
3813
+ const selectedColumns = `
3814
+ case when u.id is null then c.id else u.id end as id,
3815
+ case when u.id is null then c.device_code_hash else u.device_code_hash end as device_code_hash,
3816
+ case when u.id is null then c.user_code_hash else u.user_code_hash end as user_code_hash,
3817
+ case when u.id is null then c.client_id else u.client_id end as client_id,
3818
+ case when u.id is null then c.requested_scopes else u.requested_scopes end as requested_scopes,
3819
+ case when u.id is null then c.granted_scopes else u.granted_scopes end as granted_scopes,
3820
+ case when u.id is null then c.subject else u.subject end as subject,
3821
+ case when u.id is null then c.status else u.status end as status,
3822
+ case when u.id is null then c.issued_at else u.issued_at end as issued_at,
3823
+ case when u.id is null then c.expires_at else u.expires_at end as expires_at,
3824
+ case when u.id is null then c.poll_interval_seconds else u.poll_interval_seconds end as poll_interval_seconds,
3825
+ case when u.id is null then c.next_poll_at else u.next_poll_at end as next_poll_at,
3826
+ case when u.id is null then c.last_polled_at else u.last_polled_at end as last_polled_at,
3827
+ case when u.id is null then c.approved_at else u.approved_at end as approved_at,
3828
+ case when u.id is null then c.denied_at else u.denied_at end as denied_at,
3829
+ case when u.id is null then c.consumed_at else u.consumed_at end as consumed_at,
3830
+ case when u.id is null then c.metadata else u.metadata end as metadata`;
3831
+ return OAuthDeviceAuthorizationStore.of({
3832
+ insert: (record) => Effect.suspend(() => execute(executor, insert(authPostgresOAuthDeviceAuthorization, oauthDeviceAuthorizationValues(record)).pipe(Query.returning({ id: authPostgresOAuthDeviceAuthorization.id })))).pipe(Effect.as(true), Effect.catch((error) => postgresErrorDetails(error).code === "23505"
3833
+ ? Effect.succeed(false)
3834
+ : Effect.fail(oauthDeviceAuthorizationStoreError("insert", error)))),
3835
+ findByDeviceCodeHash: (input) => find(authPostgresOAuthDeviceAuthorization.device_code_hash, input.deviceCodeHash, "find-by-device-code-hash"),
3836
+ findByUserCodeHash: (input) => find(authPostgresOAuthDeviceAuthorization.user_code_hash, input.userCodeHash, "find-by-user-code-hash"),
3837
+ findById: (id) => find(authPostgresOAuthDeviceAuthorization.id, id, "find-by-id"),
3838
+ decide: (input) => {
3839
+ const mapError = (cause) => oauthDeviceAuthorizationStoreError("decide", cause);
3840
+ const status = input.decision === "approve" ? "approved" : "denied";
3841
+ const subject = input.decision === "approve" ? input.subject : null;
3842
+ const grantedScopes = input.decision === "approve"
3843
+ ? JSON.stringify(input.grantedScopes)
3844
+ : null;
3845
+ return sql
3846
+ .unsafe(`with candidate as materialized (
3847
+ select * from auth_oauth_device_authorization where id = $1 for update
3848
+ ), updated as (
3849
+ update auth_oauth_device_authorization d set
3850
+ status = $3,
3851
+ subject = $4,
3852
+ granted_scopes = $5::jsonb,
3853
+ approved_at = case when $3 = 'approved' then $2::bigint else null end,
3854
+ denied_at = case when $3 = 'denied' then $2::bigint else null end,
3855
+ metadata = coalesce($6::jsonb, c.metadata)
3856
+ from candidate c
3857
+ where d.id = c.id and c.status = 'pending' and c.expires_at > $2::bigint
3858
+ returning d.*
3859
+ )
3860
+ select
3861
+ case when u.id is not null then $3 when c.expires_at <= $2::bigint then 'expired' else 'already_decided' end as action,
3862
+ ${selectedColumns}
3863
+ from candidate c left join updated u on true`, [
3864
+ input.id,
3865
+ millisParam(input.decidedAt),
3866
+ status,
3867
+ subject,
3868
+ grantedScopes,
3869
+ input.metadata === undefined
3870
+ ? null
3871
+ : JSON.stringify(input.metadata),
3872
+ ])
3873
+ .pipe(Effect.mapError(mapError), Effect.flatMap((rows) => decodeMapped(() => {
3874
+ const [row] = rows;
3875
+ if (row === undefined) {
3876
+ return { status: "missing" };
3877
+ }
3878
+ const record = decodeOAuthDeviceAuthorization(row);
3879
+ if (row.action === "expired") {
3880
+ return { status: "expired", record };
3881
+ }
3882
+ if (row.action === "already_decided") {
3883
+ return {
3884
+ status: "already_decided",
3885
+ record: record,
3886
+ };
3887
+ }
3888
+ return record.status === "approved"
3889
+ ? { status: "approved", record }
3890
+ : {
3891
+ status: "denied",
3892
+ record: record,
3893
+ };
3894
+ }, mapError)));
3895
+ },
3896
+ poll: (input) => {
3897
+ const mapError = (cause) => oauthDeviceAuthorizationStoreError("poll", cause);
3898
+ return sql
3899
+ .unsafe(`with candidate as materialized (
3900
+ select d.*,
3901
+ case
3902
+ when d.consumed_at is not null then 'already_consumed'
3903
+ when d.expires_at <= $3::bigint then 'expired'
3904
+ when d.status = 'denied' then 'denied'
3905
+ when d.status = 'approved' then 'approved'
3906
+ when d.next_poll_at > $3::bigint then 'slow_down'
3907
+ when d.status = 'pending' then 'pending'
3908
+ else 'pending'
3909
+ end as action
3910
+ from auth_oauth_device_authorization d
3911
+ where d.device_code_hash = $1 and d.client_id = $2
3912
+ for update
3913
+ ), updated as (
3914
+ update auth_oauth_device_authorization d set
3915
+ poll_interval_seconds = case when c.action = 'slow_down' then c.poll_interval_seconds + 5 else c.poll_interval_seconds end,
3916
+ next_poll_at = $3::bigint + (case when c.action = 'slow_down' then c.poll_interval_seconds + 5 else c.poll_interval_seconds end) * 1000,
3917
+ last_polled_at = $3::bigint
3918
+ from candidate c
3919
+ where d.id = c.id and c.action in ('slow_down', 'pending')
3920
+ returning d.*
3921
+ )
3922
+ select c.action, ${selectedColumns}
3923
+ from candidate c left join updated u on true`, [input.deviceCodeHash, input.clientId, millisParam(input.now)])
3924
+ .pipe(Effect.mapError(mapError), Effect.flatMap((rows) => decodeMapped(() => {
3925
+ const [row] = rows;
3926
+ if (row === undefined) {
3927
+ return { status: "missing" };
3928
+ }
3929
+ const record = decodeOAuthDeviceAuthorization(row);
3930
+ switch (row.action) {
3931
+ case "expired": {
3932
+ return { status: "expired", record };
3933
+ }
3934
+ case "denied": {
3935
+ return {
3936
+ status: "denied",
3937
+ record: record,
3938
+ };
3939
+ }
3940
+ case "already_consumed": {
3941
+ return {
3942
+ status: "already_consumed",
3943
+ record: record,
3944
+ };
3945
+ }
3946
+ case "slow_down": {
3947
+ return {
3948
+ status: "slow_down",
3949
+ record,
3950
+ interval: record.pollIntervalSeconds,
3951
+ };
3952
+ }
3953
+ case "pending": {
3954
+ return {
3955
+ status: "pending",
3956
+ record: record,
3957
+ };
3958
+ }
3959
+ default: {
3960
+ return {
3961
+ status: "approved",
3962
+ record: record,
3963
+ };
3964
+ }
3965
+ }
3966
+ }, mapError)));
3967
+ },
3968
+ deleteExpired: (input) => {
3969
+ const mapError = (cause) => oauthDeviceAuthorizationStoreError("delete-expired", cause);
3970
+ return executeMapped(executor, () => Query.delete(authPostgresOAuthDeviceAuthorization).pipe(Query.where(lte(authPostgresOAuthDeviceAuthorization.expires_at, millisParam(input.beforeExpiresAt))), Query.returning({ id: authPostgresOAuthDeviceAuthorization.id })), mapError).pipe(Effect.map((rows) => rows.length));
3971
+ },
3972
+ });
3973
+ };
3974
+ export const makeEffectQbPostgresOAuthDeviceCodeTokenCommitStore = (sql) => {
3975
+ const executor = makeEffectQbPostgresExecutor(sql);
3976
+ const mapError = oauthDeviceCodeTokenCommitStoreError;
3977
+ return OAuthDeviceCodeTokenCommitStore.of({
3978
+ commit: (input) => sql
3979
+ .withTransaction(Effect.gen(function* () {
3980
+ const selected = yield* executeMapped(executor, () => selectForUpdate(oauthDeviceAuthorizationSelection, authPostgresOAuthDeviceAuthorization, eq(authPostgresOAuthDeviceAuthorization.id, input.authorization.id)), mapError);
3981
+ const existing = yield* decodeMapped(() => selected[0] === undefined
3982
+ ? undefined
3983
+ : decodeOAuthDeviceAuthorization(selected[0]), mapError);
3984
+ if (existing === undefined) {
3985
+ return { status: "missing" };
3986
+ }
3987
+ if (existing.status !== "approved") {
3988
+ return { status: "missing" };
3989
+ }
3990
+ if (existing.consumedAt !== undefined) {
3991
+ return {
3992
+ status: "already_consumed",
3993
+ record: existing,
3994
+ };
3995
+ }
3996
+ const expected = input.authorization;
3997
+ const sameArray = (left, right) => left.length === right.length &&
3998
+ left.every((value, index) => value === right[index]);
3999
+ const matchesApprovedSnapshot = existing.id === expected.id &&
4000
+ existing.deviceCodeHash === expected.deviceCodeHash &&
4001
+ existing.userCodeHash === expected.userCodeHash &&
4002
+ existing.clientId === expected.clientId &&
4003
+ sameArray(existing.requestedScopes, expected.requestedScopes) &&
4004
+ existing.subject === expected.subject &&
4005
+ sameArray(existing.grantedScopes, expected.grantedScopes) &&
4006
+ Number(existing.issuedAt) === Number(expected.issuedAt) &&
4007
+ Number(existing.expiresAt) === Number(expected.expiresAt) &&
4008
+ existing.pollIntervalSeconds === expected.pollIntervalSeconds &&
4009
+ Number(existing.nextPollAt) === Number(expected.nextPollAt) &&
4010
+ (existing.lastPolledAt === undefined
4011
+ ? expected.lastPolledAt === undefined
4012
+ : Number(existing.lastPolledAt) ===
4013
+ Number(expected.lastPolledAt)) &&
4014
+ Number(existing.approvedAt) === Number(expected.approvedAt) &&
4015
+ JSON.stringify(existing.metadata ?? null) ===
4016
+ JSON.stringify(expected.metadata ?? null);
4017
+ if (!matchesApprovedSnapshot) {
4018
+ return { status: "missing" };
4019
+ }
4020
+ if (Number(existing.expiresAt) <= Number(input.consumedAt)) {
4021
+ return {
4022
+ status: "expired",
4023
+ record: existing,
4024
+ };
4025
+ }
4026
+ yield* Effect.forEach(input.tokenRecords, (record) => executeMapped(executor, () => insert(authPostgresOAuthProviderModeToken, oauthProviderModeTokenValues(record)), mapError), { discard: true });
4027
+ const updated = yield* executeMapped(executor, () => update(authPostgresOAuthDeviceAuthorization, {
4028
+ consumed_at: millisParam(input.consumedAt),
4029
+ last_polled_at: millisParam(input.consumedAt),
4030
+ next_poll_at: millisParam(UnixMillis(Number(input.consumedAt) +
4031
+ existing.pollIntervalSeconds * 1000)),
4032
+ }).pipe(Query.where(eq(authPostgresOAuthDeviceAuthorization.id, input.authorization.id)), Query.where(eq(authPostgresOAuthDeviceAuthorization.status, "approved")), Query.where(isNull(authPostgresOAuthDeviceAuthorization.consumed_at)), Query.where(gt(authPostgresOAuthDeviceAuthorization.expires_at, millisParam(input.consumedAt))), Query.returning({
4033
+ ...oauthDeviceAuthorizationSelection,
4034
+ consumedAt: withoutNullRefinement(authPostgresOAuthDeviceAuthorization.consumed_at),
4035
+ })), mapError);
4036
+ return yield* decodeMapped(() => {
4037
+ const [row] = updated;
4038
+ if (row === undefined) {
4039
+ throw new Error("OAuth device authorization consume CAS failed");
4040
+ }
4041
+ return {
4042
+ status: "committed",
4043
+ record: decodeOAuthDeviceAuthorization(row),
4044
+ };
4045
+ }, mapError);
4046
+ }))
4047
+ .pipe(Effect.mapError((cause) => cause instanceof OAuthDeviceCodeTokenCommitStoreError
4048
+ ? cause
4049
+ : mapError(cause))),
4050
+ });
4051
+ };
3697
4052
  export const makeEffectQbPostgresOAuthClientSecretStore = (executor) => {
3698
4053
  const find = (input, operation = "find") => {
3699
4054
  const mapError = (cause) => oauthClientSecretStoreError(operation, cause);
@@ -4267,6 +4622,8 @@ export const makeEffectQbPostgresCoreStores = (sql, options = {}) => {
4267
4622
  oauthClientStore: makeEffectQbPostgresOAuthClientStore(executor),
4268
4623
  oauthConsentStore: makeEffectQbPostgresOAuthConsentStore(executor),
4269
4624
  oauthAuthorizationCodeStore: makeEffectQbPostgresOAuthAuthorizationCodeStore(executor, sql),
4625
+ oauthDeviceAuthorizationStore: makeEffectQbPostgresOAuthDeviceAuthorizationStore(executor, sql),
4626
+ oauthDeviceCodeTokenCommitStore: makeEffectQbPostgresOAuthDeviceCodeTokenCommitStore(sql),
4270
4627
  oauthClientSecretStore: makeEffectQbPostgresOAuthClientSecretStore(executor),
4271
4628
  oauthProviderModeTokenStore: makeEffectQbPostgresOAuthProviderModeTokenStore(executor),
4272
4629
  oauthProviderTokenVault: makeEffectQbPostgresOAuthProviderTokenVault(executor),
@@ -4275,6 +4632,6 @@ export const makeEffectQbPostgresCoreStores = (sql, options = {}) => {
4275
4632
  };
4276
4633
  export const EffectQbPostgresCoreStoresLive = (sql, options = {}) => {
4277
4634
  const stores = makeEffectQbPostgresCoreStores(sql, options);
4278
- return Layer.mergeAll(Layer.succeed(UserStore)(stores.userStore), Layer.succeed(IdentityStore)(stores.identityStore), Layer.succeed(RegistrationStore)(stores.registrationStore), Layer.succeed(CredentialStore)(stores.credentialStore), Layer.succeed(PasskeyCredentialStore)(stores.passkeyCredentialStore), Layer.succeed(SessionStore)(stores.sessionStore), Layer.succeed(VerificationStore)(stores.verificationStore), Layer.succeed(AuditLogStore)(stores.auditLogStore), Layer.succeed(LoginApprovalReviewStore)(stores.loginApprovalReviewStore), Layer.succeed(LoginRiskHistory)(stores.loginRiskHistoryStore), Layer.succeed(TrustedDeviceStore)(stores.trustedDeviceStore), Layer.succeed(DomainVerificationStore)(stores.domainVerificationStore), Layer.succeed(SecurityTimelineStore)(stores.securityTimelineStore), Layer.succeed(WebhookOutboxStore)(stores.webhookOutboxStore), Layer.succeed(WebhookReplayStore)(stores.webhookReplayStore), Layer.succeed(TotpFactorStore)(stores.totpFactorStore), Layer.succeed(TotpSessionRotateStore)(stores.totpSessionRotateStore), Layer.succeed(TotpLoginCommitStore)(stores.totpLoginCommitStore), Layer.succeed(RecoveryCodeStore)(stores.recoveryCodeStore), Layer.succeed(RecoveryCodeSessionRotateStore)(stores.recoveryCodeSessionRotateStore), Layer.succeed(RecoveryCodeLoginCommitStore)(stores.recoveryCodeLoginCommitStore), Layer.succeed(ApiKeyStore)(stores.apiKeyStore), Layer.succeed(RefreshTokenStore)(stores.refreshTokenStore), Layer.succeed(JwtRevocationStore)(stores.jwtRevocationStore), Layer.succeed(OAuthAccountStore)(stores.oauthAccountStore), Layer.succeed(OAuthIdentityBridgeStore)(stores.oauthIdentityBridgeStore), Layer.succeed(OAuthClientStore)(stores.oauthClientStore), Layer.succeed(OAuthConsentStore)(stores.oauthConsentStore), Layer.succeed(OAuthAuthorizationCodeStore)(stores.oauthAuthorizationCodeStore), Layer.succeed(OAuthClientSecretStore)(stores.oauthClientSecretStore), Layer.succeed(OAuthProviderModeTokenStore)(stores.oauthProviderModeTokenStore), Layer.succeed(OAuthProviderTokenVault)(stores.oauthProviderTokenVault), Layer.succeed(PermissionStore)(stores.permissionStore));
4635
+ return Layer.mergeAll(Layer.succeed(UserStore)(stores.userStore), Layer.succeed(IdentityStore)(stores.identityStore), Layer.succeed(RegistrationStore)(stores.registrationStore), Layer.succeed(CredentialStore)(stores.credentialStore), Layer.succeed(PasskeyCredentialStore)(stores.passkeyCredentialStore), Layer.succeed(SessionStore)(stores.sessionStore), Layer.succeed(VerificationStore)(stores.verificationStore), Layer.succeed(AuditLogStore)(stores.auditLogStore), Layer.succeed(LoginApprovalReviewStore)(stores.loginApprovalReviewStore), Layer.succeed(LoginRiskHistory)(stores.loginRiskHistoryStore), Layer.succeed(TrustedDeviceStore)(stores.trustedDeviceStore), Layer.succeed(DomainVerificationStore)(stores.domainVerificationStore), Layer.succeed(SecurityTimelineStore)(stores.securityTimelineStore), Layer.succeed(WebhookOutboxStore)(stores.webhookOutboxStore), Layer.succeed(WebhookReplayStore)(stores.webhookReplayStore), Layer.succeed(TotpFactorStore)(stores.totpFactorStore), Layer.succeed(TotpSessionRotateStore)(stores.totpSessionRotateStore), Layer.succeed(TotpLoginCommitStore)(stores.totpLoginCommitStore), Layer.succeed(RecoveryCodeStore)(stores.recoveryCodeStore), Layer.succeed(RecoveryCodeSessionRotateStore)(stores.recoveryCodeSessionRotateStore), Layer.succeed(RecoveryCodeLoginCommitStore)(stores.recoveryCodeLoginCommitStore), Layer.succeed(ApiKeyStore)(stores.apiKeyStore), Layer.succeed(RefreshTokenStore)(stores.refreshTokenStore), Layer.succeed(JwtRevocationStore)(stores.jwtRevocationStore), Layer.succeed(OAuthAccountStore)(stores.oauthAccountStore), Layer.succeed(OAuthIdentityBridgeStore)(stores.oauthIdentityBridgeStore), Layer.succeed(OAuthClientStore)(stores.oauthClientStore), Layer.succeed(OAuthConsentStore)(stores.oauthConsentStore), Layer.succeed(OAuthAuthorizationCodeStore)(stores.oauthAuthorizationCodeStore), Layer.succeed(OAuthDeviceAuthorizationStore)(stores.oauthDeviceAuthorizationStore), Layer.succeed(OAuthDeviceCodeTokenCommitStore)(stores.oauthDeviceCodeTokenCommitStore), Layer.succeed(OAuthClientSecretStore)(stores.oauthClientSecretStore), Layer.succeed(OAuthProviderModeTokenStore)(stores.oauthProviderModeTokenStore), Layer.succeed(OAuthProviderTokenVault)(stores.oauthProviderTokenVault), Layer.succeed(PermissionStore)(stores.permissionStore));
4279
4636
  };
4280
4637
  //# sourceMappingURL=EffectQbPostgresStorage.js.map